blob: d025ea9f7d5c8548b7bf71b404a4c09c29b29d94 [file] [log] [blame]
Philipp0b11db72016-08-01 18:13:40 +02001/*
2 * SpanDSP - a series of DSP components for telephony
3 *
4 * v42bis.c
5 *
6 * Written by Steve Underwood <steveu@coppice.org>
7 *
8 * Copyright (C) 2005, 2011 Steve Underwood
9 *
10 * All rights reserved.
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU Lesser General Public License version 2.1,
14 * as published by the Free Software Foundation.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU Lesser General Public License for more details.
20 *
21 * You should have received a copy of the GNU Lesser General Public
22 * License along with this program; if not, write to the Free Software
23 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24 */
25
26/* THIS IS A WORK IN PROGRESS. IT IS NOT FINISHED.
27 Currently it performs the core compression and decompression functions OK.
28 However, a number of the bells and whistles in V.42bis are incomplete. */
29
30/*! \file */
31
32#if defined(HAVE_CONFIG_H)
33#include "config.h"
34#endif
35
36#include <stdio.h>
37#include <stdlib.h>
38#include <inttypes.h>
39#include <string.h>
40#include <errno.h>
41#include <fcntl.h>
42#include <ctype.h>
43#include <assert.h>
44
45#include "spandsp/telephony.h"
46#include "spandsp/logging.h"
47#include "spandsp/bit_operations.h"
48#include "spandsp/async.h"
49#include "spandsp/v42bis.h"
50
51#include "spandsp/private/logging.h"
52#include "spandsp/private/v42bis.h"
53
54/* Fixed parameters from the spec. */
55/* Character size (bits) */
56#define V42BIS_N3 8
57/* Number of characters in the alphabet */
58#define V42BIS_N4 256
59/* Index number of first dictionary entry used to store a string */
60#define V42BIS_N5 (V42BIS_N4 + V42BIS_N6)
61/* Number of control codewords */
62#define V42BIS_N6 3
63/* V.42bis/9.2 */
64#define V42BIS_ESC_STEP 51
65
66/* Compreeibility monitoring parameters for assessing automated switches between
67 transparent and compressed mode */
68#define COMPRESSIBILITY_MONITOR (256*V42BIS_N3)
69#define COMPRESSIBILITY_MONITOR_HYSTERESIS 11
70
71/* Control code words in compressed mode */
72enum
73{
74 V42BIS_ETM = 0, /* Enter transparent mode */
75 V42BIS_FLUSH = 1, /* Flush data */
76 V42BIS_STEPUP = 2 /* Step up codeword size */
77};
78
79/* Command codes in transparent mode */
80enum
81{
82 V42BIS_ECM = 0, /* Enter compression mode */
83 V42BIS_EID = 1, /* Escape character in data */
84 V42BIS_RESET = 2 /* Force reinitialisation */
85};
86
87static __inline__ void push_octet(v42bis_comp_state_t *s, int octet)
88{
89 s->output_buf[s->output_octet_count++] = (uint8_t) octet;
90 if (s->output_octet_count >= s->max_output_len)
91 {
92 s->handler(s->user_data, s->output_buf, s->output_octet_count);
93 s->output_octet_count = 0;
94 }
95}
96/*- End of function --------------------------------------------------------*/
97
98static __inline__ void push_octets(v42bis_comp_state_t *s, const uint8_t buf[], int len)
99{
100 int i;
101 int chunk;
102
103 i = 0;
104 while ((s->output_octet_count + len - i) >= s->max_output_len)
105 {
106 chunk = s->max_output_len - s->output_octet_count;
107 memcpy(&s->output_buf[s->output_octet_count], &buf[i], chunk);
108 s->handler(s->user_data, s->output_buf, s->max_output_len);
109 s->output_octet_count = 0;
110 i += chunk;
111 }
112 chunk = len - i;
113 if (chunk > 0)
114 {
115 memcpy(&s->output_buf[s->output_octet_count], &buf[i], chunk);
116 s->output_octet_count += chunk;
117 }
118}
119/*- End of function --------------------------------------------------------*/
120
121static __inline__ void push_compressed_code(v42bis_comp_state_t *s, int code)
122{
123 s->bit_buffer |= code << s->bit_count;
124 s->bit_count += s->v42bis_parm_c2;
125 while (s->bit_count >= 8)
126 {
127 push_octet(s, s->bit_buffer & 0xFF);
128 s->bit_buffer >>= 8;
129 s->bit_count -= 8;
130 }
131}
132/*- End of function --------------------------------------------------------*/
133
134static __inline__ void push_octet_alignment(v42bis_comp_state_t *s)
135{
136 if ((s->bit_count & 7))
137 {
138 s->bit_count += (8 - (s->bit_count & 7));
139 while (s->bit_count >= 8)
140 {
141 push_octet(s, s->bit_buffer & 0xFF);
142 s->bit_buffer >>= 8;
143 s->bit_count -= 8;
144 }
145 }
146}
147/*- End of function --------------------------------------------------------*/
148
149static __inline__ void flush_octets(v42bis_comp_state_t *s)
150{
151 if (s->output_octet_count > 0)
152 {
153 s->handler(s->user_data, s->output_buf, s->output_octet_count);
154 s->output_octet_count = 0;
155 }
156}
157/*- End of function --------------------------------------------------------*/
158
159static void dictionary_init(v42bis_comp_state_t *s)
160{
161 int i;
162
163 memset(s->dict, 0, sizeof(s->dict));
164 for (i = 0; i < V42BIS_N4; i++)
165 s->dict[i + V42BIS_N6].node_octet = i;
166 s->v42bis_parm_c1 = V42BIS_N5;
167 s->v42bis_parm_c2 = V42BIS_N3 + 1;
168 s->v42bis_parm_c3 = V42BIS_N4 << 1;
169 s->last_matched = 0;
170 s->update_at = 0;
171 s->last_added = 0;
172 s->bit_buffer = 0;
173 s->bit_count = 0;
174 s->flushed_length = 0;
175 s->string_length = 0;
176 s->escape_code = 0;
177 s->transparent = TRUE;
178 s->escaped = FALSE;
179 s->compression_performance = COMPRESSIBILITY_MONITOR;
180}
181/*- End of function --------------------------------------------------------*/
182
183static uint16_t match_octet(v42bis_comp_state_t *s, uint16_t at, uint8_t octet)
184{
185 uint16_t e;
186
187 if (at == 0)
188 return octet + V42BIS_N6;
189 e = s->dict[at].child;
190 while (e)
191 {
192 if (s->dict[e].node_octet == octet)
193 return e;
194 e = s->dict[e].next;
195 }
196 return 0;
197}
198/*- End of function --------------------------------------------------------*/
199
200static uint16_t add_octet_to_dictionary(v42bis_comp_state_t *s, uint16_t at, uint8_t octet)
201{
202 uint16_t newx;
203 uint16_t next;
204 uint16_t e;
205
206 newx = s->v42bis_parm_c1;
207 s->dict[newx].node_octet = octet;
208 s->dict[newx].parent = at;
209 s->dict[newx].child = 0;
210 s->dict[newx].next = s->dict[at].child;
211 s->dict[at].child = newx;
212 next = newx;
213 /* 6.5 Recovering a dictionary entry to use next */
214 do
215 {
216 /* 6.5(a) and (b) */
217 if (++next == s->v42bis_parm_n2)
218 next = V42BIS_N5;
219 }
220 while (s->dict[next].child);
221 /* 6.5(c) We need to reuse a leaf node */
222 if (s->dict[next].parent)
223 {
224 /* 6.5(d) Detach the leaf node from its parent, and re-use it */
225 e = s->dict[next].parent;
226 if (s->dict[e].child == next)
227 {
228 s->dict[e].child = s->dict[next].next;
229 }
230 else
231 {
232 e = s->dict[e].child;
233 while (s->dict[e].next != next)
234 e = s->dict[e].next;
235 s->dict[e].next = s->dict[next].next;
236 }
237 }
238 s->v42bis_parm_c1 = next;
239 return newx;
240}
241/*- End of function --------------------------------------------------------*/
242
243static void send_string(v42bis_comp_state_t *s)
244{
245 push_octets(s, s->string, s->string_length);
246 s->string_length = 0;
247 s->flushed_length = 0;
248}
249/*- End of function --------------------------------------------------------*/
250
251static void expand_codeword_to_string(v42bis_comp_state_t *s, uint16_t code)
252{
253 int i;
254 uint16_t p;
255
256 /* Work out the length */
257 for (i = 0, p = code; p; i++)
258 p = s->dict[p].parent;
259 s->string_length += i;
260 /* Now expand the known length of string */
261 i = s->string_length - 1;
262 for (p = code; p; )
263 {
264 s->string[i--] = s->dict[p].node_octet;
265 p = s->dict[p].parent;
266 }
267}
268/*- End of function --------------------------------------------------------*/
269
270static void send_encoded_data(v42bis_comp_state_t *s, uint16_t code)
271{
272 int i;
273
274 /* Update compressibility metric */
275 /* Integrate at the compressed bit rate, and leak at the pre-compression bit rate */
276 s->compression_performance += (s->v42bis_parm_c2 - s->compression_performance*s->string_length*V42BIS_N3/COMPRESSIBILITY_MONITOR);
277 if (s->transparent)
278 {
279 for (i = 0; i < s->string_length; i++)
280 {
281 push_octet(s, s->string[i]);
282 if (s->string[i] == s->escape_code)
283 {
284 push_octet(s, V42BIS_EID);
285 s->escape_code += V42BIS_ESC_STEP;
286 }
287 }
288 }
289 else
290 {
291 /* Allow for any escape octets in the string */
292 for (i = 0; i < s->string_length; i++)
293 {
294 if (s->string[i] == s->escape_code)
295 s->escape_code += V42BIS_ESC_STEP;
296 }
297 /* 7.4 Encoding - we now have the longest matchable string, and will need to output the code for it. */
298 while (code >= s->v42bis_parm_c3)
299 {
300 /* We need to increase the codeword size */
301 /* 7.4(a) */
302 push_compressed_code(s, V42BIS_STEPUP);
303 /* 7.4(b) */
304 s->v42bis_parm_c2++;
305 /* 7.4(c) */
306 s->v42bis_parm_c3 <<= 1;
307 /* 7.4(d) this might need to be repeated, so we loop */
308 }
309 /* 7.5 Transfer - output the last state of the string */
310 push_compressed_code(s, code);
311 }
312 s->string_length = 0;
313 s->flushed_length = 0;
314}
315/*- End of function --------------------------------------------------------*/
316
317static void go_compressed(v42bis_state_t *ss)
318{
319 v42bis_comp_state_t *s;
320
321 s = &ss->compress;
322 if (!s->transparent)
323 return;
324 span_log(&ss->logging, SPAN_LOG_FLOW, "Changing to compressed mode\n");
325 /* Switch out of transparent now, between codes. We need to send the octet which did not
326 match, just before switching. */
327 if (s->last_matched)
328 {
329 s->update_at = s->last_matched;
330 send_encoded_data(s, s->last_matched);
331 s->last_matched = 0;
332 }
333 push_octet(s, s->escape_code);
334 push_octet(s, V42BIS_ECM);
335 s->bit_buffer = 0;
336 s->transparent = FALSE;
337}
338/*- End of function --------------------------------------------------------*/
339
340static void go_transparent(v42bis_state_t *ss)
341{
342 v42bis_comp_state_t *s;
343
344 s = &ss->compress;
345 if (s->transparent)
346 return;
347 span_log(&ss->logging, SPAN_LOG_FLOW, "Changing to transparent mode\n");
348 /* Switch into transparent now, between codes, and the unmatched octet should
349 go out in transparent mode, just below */
350 if (s->last_matched)
351 {
352 s->update_at = s->last_matched;
353 send_encoded_data(s, s->last_matched);
354 s->last_matched = 0;
355 }
356 s->last_added = 0;
357 push_compressed_code(s, V42BIS_ETM);
358 push_octet_alignment(s);
359 s->transparent = TRUE;
360}
361/*- End of function --------------------------------------------------------*/
362
363static void monitor_for_mode_change(v42bis_state_t *ss)
364{
365 v42bis_comp_state_t *s;
366
367 s = &ss->compress;
368 switch (s->compression_mode)
369 {
370 case V42BIS_COMPRESSION_MODE_DYNAMIC:
371 /* 7.8 Data compressibility test */
372 if (s->transparent)
373 {
374 if (s->compression_performance < COMPRESSIBILITY_MONITOR - COMPRESSIBILITY_MONITOR_HYSTERESIS)
375 {
376 /* 7.8.1 Transition to compressed mode */
377 go_compressed(ss);
378 }
379 }
380 else
381 {
382 if (s->compression_performance > COMPRESSIBILITY_MONITOR)
383 {
384 /* 7.8.2 Transition to transparent mode */
385 go_transparent(ss);
386 }
387 }
388 /* 7.8.3 Reset function - TODO */
389 break;
390 case V42BIS_COMPRESSION_MODE_ALWAYS:
391 if (s->transparent)
392 go_compressed(ss);
393 break;
394 case V42BIS_COMPRESSION_MODE_NEVER:
395 if (!s->transparent)
396 go_transparent(ss);
397 break;
398 }
399}
400/*- End of function --------------------------------------------------------*/
401
402static int v42bis_comp_init(v42bis_comp_state_t *s,
403 int p1,
404 int p2,
405 put_msg_func_t handler,
406 void *user_data,
407 int max_output_len)
408{
409 memset(s, 0, sizeof(*s));
410 s->v42bis_parm_n2 = p1;
411 s->v42bis_parm_n7 = p2;
412 s->handler = handler;
413 s->user_data = user_data;
414 s->max_output_len = (max_output_len < V42BIS_MAX_OUTPUT_LENGTH) ? max_output_len : V42BIS_MAX_OUTPUT_LENGTH;
415 s->output_octet_count = 0;
416 dictionary_init(s);
417 return 0;
418}
419/*- End of function --------------------------------------------------------*/
420
421static int comp_exit(v42bis_comp_state_t *s)
422{
423 s->v42bis_parm_n2 = 0;
424 return 0;
425}
426/*- End of function --------------------------------------------------------*/
427
428SPAN_DECLARE(int) v42bis_compress(v42bis_state_t *ss, const uint8_t buf[], int len)
429{
430 v42bis_comp_state_t *s;
431 int i;
432 uint16_t code;
433
434 s = &ss->compress;
435 if (!s->v42bis_parm_p0)
436 {
437 /* Compression is off - just push the incoming data out */
438 push_octets(s, buf, len);
439 return 0;
440 }
441 for (i = 0; i < len; )
442 {
443 /* 6.4 Add the string to the dictionary */
444 if (s->update_at)
445 {
446 if (match_octet(s, s->update_at, buf[i]) == 0)
447 s->last_added = add_octet_to_dictionary(s, s->update_at, buf[i]);
448 s->update_at = 0;
449 }
450 /* Match string */
451 while (i < len)
452 {
453 code = match_octet(s, s->last_matched, buf[i]);
454 if (code == 0)
455 {
456 s->update_at = s->last_matched;
457 send_encoded_data(s, s->last_matched);
458 s->last_matched = 0;
459 break;
460 }
461 if (code == s->last_added)
462 {
463 s->last_added = 0;
464 send_encoded_data(s, s->last_matched);
465 s->last_matched = 0;
466 break;
467 }
468 s->last_matched = code;
469 /* 6.3(b) If the string matches a dictionary entry, and the entry is not that entry
470 created by the last invocation of the string matching procedure, then the
471 next character shall be read and appended to the string and this step
472 repeated. */
473 s->string[s->string_length++] = buf[i++];
474 /* 6.4(a) The string must not exceed N7 in length */
475 if (s->string_length + s->flushed_length == s->v42bis_parm_n7)
476 {
477 send_encoded_data(s, s->last_matched);
478 s->last_matched = 0;
479 break;
480 }
481 }
482 monitor_for_mode_change(ss);
483 }
484 return 0;
485}
486/*- End of function --------------------------------------------------------*/
487
488SPAN_DECLARE(int) v42bis_compress_flush(v42bis_state_t *ss)
489{
490 v42bis_comp_state_t *s;
491 int len;
492
493 s = &ss->compress;
494 if (s->update_at)
495 return 0;
496 if (s->last_matched)
497 {
498 len = s->string_length;
499 send_encoded_data(s, s->last_matched);
500 s->flushed_length += len;
501 }
502 if (!s->transparent)
503 {
504 s->update_at = s->last_matched;
505 s->last_matched = 0;
506 s->flushed_length = 0;
507 push_compressed_code(s, V42BIS_FLUSH);
508 push_octet_alignment(s);
509 }
510 flush_octets(s);
511 return 0;
512}
513/*- End of function --------------------------------------------------------*/
514
515SPAN_DECLARE(int) v42bis_decompress(v42bis_state_t *ss, const uint8_t buf[], int len)
516{
517 v42bis_comp_state_t *s;
518 int i;
519 int j;
520 int yyy;
521 uint16_t code;
522 uint16_t p;
523 uint8_t ch;
524 uint8_t in;
525
526 s = &ss->decompress;
527 if (!s->v42bis_parm_p0)
528 {
529 /* Compression is off - just push the incoming data out */
530 push_octets(s, buf, len);
531 return 0;
532 }
533 for (i = 0; i < len; )
534 {
535 if (s->transparent)
536 {
537 in = buf[i];
538 if (s->escaped)
539 {
540 /* Command */
541 s->escaped = FALSE;
542 switch (in)
543 {
544 case V42BIS_ECM:
545 /* Enter compressed mode */
546 span_log(&ss->logging, SPAN_LOG_FLOW, "Hit V42BIS_ECM\n");
547 send_string(s);
548 s->transparent = FALSE;
549 s->update_at = s->last_matched;
550 s->last_matched = 0;
551 i++;
552 continue;
553 case V42BIS_EID:
554 /* Escape symbol */
555 span_log(&ss->logging, SPAN_LOG_FLOW, "Hit V42BIS_EID\n");
556 in = s->escape_code;
557 s->escape_code += V42BIS_ESC_STEP;
558 break;
559 case V42BIS_RESET:
560 /* Reset dictionary */
561 span_log(&ss->logging, SPAN_LOG_FLOW, "Hit V42BIS_RESET\n");
562 /* TODO: */
563 send_string(s);
564 dictionary_init(s);
565 i++;
566 continue;
567 default:
568 span_log(&ss->logging, SPAN_LOG_FLOW, "Hit V42BIS_???? - %" PRIu32 "\n", in);
569 return -1;
570 }
571 }
572 else if (in == s->escape_code)
573 {
574 s->escaped = TRUE;
575 i++;
576 continue;
577 }
578
579 yyy = TRUE;
580 for (j = 0; j < 2 && yyy; j++)
581 {
582 if (s->update_at)
583 {
584 if (match_octet(s, s->update_at, in) == 0)
585 s->last_added = add_octet_to_dictionary(s, s->update_at, in);
586 s->update_at = 0;
587 }
588
589 code = match_octet(s, s->last_matched, in);
590 if (code == 0)
591 {
592 s->update_at = s->last_matched;
593 send_string(s);
594 s->last_matched = 0;
595 }
596 else if (code == s->last_added)
597 {
598 s->last_added = 0;
599 send_string(s);
600 s->last_matched = 0;
601 }
602 else
603 {
604 s->last_matched = code;
605 s->string[s->string_length++] = in;
606 if (s->string_length + s->flushed_length == s->v42bis_parm_n7)
607 {
608 send_string(s);
609 s->last_matched = 0;
610 }
611 i++;
612 yyy = FALSE;
613 }
614 }
615 }
616 else
617 {
618 /* Get code from input */
619 while (s->bit_count < s->v42bis_parm_c2 && i < len)
620 {
621 s->bit_buffer |= buf[i++] << s->bit_count;
622 s->bit_count += 8;
623 }
624 if (s->bit_count < s->v42bis_parm_c2)
625 continue;
626 code = s->bit_buffer & ((1 << s->v42bis_parm_c2) - 1);
627 s->bit_buffer >>= s->v42bis_parm_c2;
628 s->bit_count -= s->v42bis_parm_c2;
629
630 if (code < V42BIS_N6)
631 {
632 /* We have a control code. */
633 switch (code)
634 {
635 case V42BIS_ETM:
636 /* Enter transparent mode */
637 span_log(&ss->logging, SPAN_LOG_FLOW, "Hit V42BIS_ETM\n");
638 s->bit_count = 0;
639 s->transparent = TRUE;
640 s->last_matched = 0;
641 s->last_added = 0;
642 break;
643 case V42BIS_FLUSH:
644 /* Flush signal */
645 span_log(&ss->logging, SPAN_LOG_FLOW, "Hit V42BIS_FLUSH\n");
646 s->bit_count = 0;
647 break;
648 case V42BIS_STEPUP:
649 /* Increase code word size */
650 span_log(&ss->logging, SPAN_LOG_FLOW, "Hit V42BIS_STEPUP\n");
651 s->v42bis_parm_c2++;
652 s->v42bis_parm_c3 <<= 1;
653 if (s->v42bis_parm_c2 > (s->v42bis_parm_n2 >> 3))
654 return -1;
655 break;
656 }
657 continue;
658 }
659 /* Regular codeword */
660 if (code == s->v42bis_parm_c1)
661 return -1;
662 expand_codeword_to_string(s, code);
663 if (s->update_at)
664 {
665 ch = s->string[0];
666 if ((p = match_octet(s, s->update_at, ch)) == 0)
667 {
668 s->last_added = add_octet_to_dictionary(s, s->update_at, ch);
669 if (code == s->v42bis_parm_c1)
670 return -1;
671 }
672 else if (p == s->last_added)
673 {
674 s->last_added = 0;
675 }
676 }
677 s->update_at = ((s->string_length + s->flushed_length) == s->v42bis_parm_n7) ? 0 : code;
678 /* Allow for any escapes which may be in this string */
679 for (j = 0; j < s->string_length; j++)
680 {
681 if (s->string[j] == s->escape_code)
682 s->escape_code += V42BIS_ESC_STEP;
683 }
684 send_string(s);
685 }
686 }
687 return 0;
688}
689/*- End of function --------------------------------------------------------*/
690
691SPAN_DECLARE(int) v42bis_decompress_flush(v42bis_state_t *ss)
692{
693 v42bis_comp_state_t *s;
694 int len;
695
696 s = &ss->decompress;
697 len = s->string_length;
698 send_string(s);
699 s->flushed_length += len;
700 flush_octets(s);
701 return 0;
702}
703/*- End of function --------------------------------------------------------*/
704
705SPAN_DECLARE(void) v42bis_compression_control(v42bis_state_t *s, int mode)
706{
707 s->compress.compression_mode = mode;
708}
709/*- End of function --------------------------------------------------------*/
710
711SPAN_DECLARE(v42bis_state_t *) v42bis_init(v42bis_state_t *s,
712 int negotiated_p0,
713 int negotiated_p1,
714 int negotiated_p2,
715 put_msg_func_t encode_handler,
716 void *encode_user_data,
717 int max_encode_len,
718 put_msg_func_t decode_handler,
719 void *decode_user_data,
720 int max_decode_len)
721{
722 int ret;
723
724 if (negotiated_p1 < V42BIS_MIN_DICTIONARY_SIZE || negotiated_p1 > 65535)
725 return NULL;
726 if (negotiated_p2 < V42BIS_MIN_STRING_SIZE || negotiated_p2 > V42BIS_MAX_STRING_SIZE)
727 return NULL;
728 if (s == NULL)
729 {
730 if ((s = (v42bis_state_t *) malloc(sizeof(*s))) == NULL)
731 return NULL;
732 }
733 memset(s, 0, sizeof(*s));
734 span_log_init(&s->logging, SPAN_LOG_NONE, NULL);
735 span_log_set_protocol(&s->logging, "V.42bis");
736
737 if ((ret = v42bis_comp_init(&s->compress, negotiated_p1, negotiated_p2, encode_handler, encode_user_data, max_encode_len)))
738 return NULL;
739 if ((ret = v42bis_comp_init(&s->decompress, negotiated_p1, negotiated_p2, decode_handler, decode_user_data, max_decode_len)))
740 {
741 comp_exit(&s->compress);
742 return NULL;
743 }
744 s->compress.v42bis_parm_p0 = negotiated_p0 & 2;
745 s->decompress.v42bis_parm_p0 = negotiated_p0 & 1;
746
747 return s;
748}
749/*- End of function --------------------------------------------------------*/
750
751SPAN_DECLARE(int) v42bis_release(v42bis_state_t *s)
752{
753 return 0;
754}
755/*- End of function --------------------------------------------------------*/
756
757SPAN_DECLARE(int) v42bis_free(v42bis_state_t *s)
758{
759 comp_exit(&s->compress);
760 comp_exit(&s->decompress);
761 return 0;
762}
763/*- End of function --------------------------------------------------------*/
764/*- End of file ------------------------------------------------------------*/