D-Modem/slmodemd/modem_comp.c
2022-12-01 21:40:04 +08:00

888 lines
21 KiB
C

/*
*
* Copyright (c) 2002, Smart Link Ltd.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* 3. Neither the name of the Smart Link Ltd. nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
/*
*
* modem_comp.c -- modem compressor module.
*
* Author: Sasha K (sashak@smlink.com)
*
*
*/
#include <modem.h>
#include <modem_debug.h>
/*
* TODO list:
* - modify interface: clean temporary out buffs
* - char char encoding/decoding
* - simplify and clean code:
* - decoder
* - encoder test compression case, do it after string finishing
* - preformance trace and optimize
*
*/
//#define COMP_TEST 1
//#define COMP_DEBUG 1
/* V42bis parameters limitation */
#define MIN_DICT_SIZE 512 /* min dictionary size enabled by protocol */
#define MIN_STR_SIZE 6 /* min string size enabled by protocol */
#define MAX_STR_SIZE 250 /* max string size enabled by protocol */
/* V42bis constants */
#define CHAR_SIZE 8 /* char size (bits) N3 */
#define TOTAL_CHARS 256 /* number of chars N4 */
#define TOTAL_CTRLS 3 /* number of control cw N6 */
#define FIRST_ENTRY TOTAL_CTRLS /* first dictionary entry */
#define FIRST_FREE TOTAL_CHARS+FIRST_ENTRY /* initial val of free entry N5 */
/* control codewords */
#define CCW_ETM 0 /* Enter transparent mode */
#define CCW_FLUSH 1 /* Flash data */
#define CCW_STEPUP 2 /* Step up codeword size */
/* command codes */
#define CC_ECM 0 /* Enter compression mode */
#define CC_EID 1 /* Escape charactrer in data */
#define CC_RESET 2 /* Force reinitialization */
#define ESCAPE_STEP 51 /* escape char value step */
#define TEST_SLICE 256 /* compression test slice (bytes) */
/* fixme: use get pages */
#define COMP_ALLOC(size) malloc(size)
#define COMP_FREE(mem) free(mem)
/* debug stuff */
#if COMP_DEBUG
#define INLINE_FUNC static
#define COMP_DBG(fmt,arg...) dprintf("comp %d/%d: " fmt , \
c->cmp_bits , c->raw_bits , ##arg)
#if 1
#define COMP_DBG1(fmt,arg...)
#else
#define COMP_DBG1(fmt,arg...) COMP_DBG(fmt , ##arg)
static char *show_string(struct comp_state *c, u16 cw)
{
static char _str[COMP_MAX_STRING+1];
int j;
_str[COMP_MAX_STRING] = 0;
for(j = COMP_MAX_STRING-1; cw && j >= 0 ; j--) {
_str[j] = c->dict[cw].ch;
cw = c->dict[cw].parent;
}
return _str+j+1;
}
#endif // 0
#else /* !COMP_DEBUG */
#define INLINE_FUNC extern inline
#define COMP_DBG(fmt,arg...)
#define COMP_DBG1(fmt,arg...)
#endif /* !COMP_DEBUG */
#define COMP_ERR(fmt,arg...) eprintf("err: " fmt , ##arg)
/*
* common procedures
*
*/
/* initialize dictionary and params */
static void dict_init(struct comp_state *c)
{
int i;
for(i=0;i<c->dict_size;i++) {
c->dict[i].parent = 0;
c->dict[i].child = 0;
c->dict[i].next = 0;
}
c->next_free = FIRST_FREE;
c->last_matched = 0;
c->update_at = 0;
c->last_added = 0;
c->cw_size = CHAR_SIZE + 1;
c->threshold = TOTAL_CHARS<<1;
c->bit_len = 0;
c->flushed_len = 0;
c->str_len = 0;
c->escape_char = 0;
c->cmp_last = TEST_SLICE<<3;
c->mode = TRANSPARENT;
c->escape = 0;
}
/* match one char */
INLINE_FUNC u16 match_char(struct comp_state *c, u16 at, u8 ch)
{
register u16 e;
if (!at)
return ch + FIRST_ENTRY;
e = c->dict[at].child;
while(e) {
if (c->dict[e].ch == ch)
return e;
e = c->dict[e].next;
}
return 0;
}
/* add one char */
INLINE_FUNC u16 add_char(struct comp_state *c, u16 at, u8 ch)
{
register u16 new = c->next_free;
register u16 next;
/* add */
c->dict[new].ch = ch;
c->dict[new].parent = at;
c->dict[new].child = 0;
c->dict[new].next = c->dict[at].child;
c->dict[at].child = new;
/* update next free */;
next = new;
do {
if (++next == c->dict_size)
next = FIRST_FREE;
} while(c->dict[next].child);
if (c->dict[next].parent) {
u16 e = c->dict[next].parent;
if(c->dict[e].child == next)
c->dict[e].child = c->dict[next].next;
else {
e = c->dict[e].child;
while(c->dict[e].next != next)
e = c->dict[e].next;
c->dict[e].next = c->dict[next].next;
}
}
c->next_free = next;
return new;
}
/*
* decoder
*
*/
/* out decoded string */
INLINE_FUNC int send_string(struct comp_state *c)
{
int ret;
#if 0
c->str_data[c->str_len] = 0;
COMP_DBG("out string %d: '%s'\n",c->last_matched,c->str_data);
#endif
memcpy(c->obuf+c->olen,c->str_data,c->str_len);
ret = c->str_len;
c->olen += ret;
c->str_len = 0;
c->flushed_len = 0;
return ret;
}
/* receive encoded data */
INLINE_FUNC int recv_data(struct comp_state *c, u16 cw)
{
int i,ret = 0;
u16 p = cw;
COMP_DBG1("dec recv %d: '%s'\n",cw,show_string(c,cw));
for (ret = 0 ; p ; ret++) {
p = c->dict[p].parent;
}
p = cw;
i = c->str_len + ret - 1;
while(p) {
c->str_data[i--] = c->dict[p].ch;
p = c->dict[p].parent;
}
c->str_len += ret;
return ret;
}
/* flush decoder */
int comp_flush_decoder(struct comp_state *c)
{
int ret, total_len;
total_len = c->flushed_len + c->str_len;
ret = send_string(c);
c->flushed_len = total_len;
if (ret) {
COMP_DBG("flush_decoder: %d...\n", ret);
}
return ret;
}
/* decoder */
int comp_decode(struct comp_state *c, u8 *in, int n)
{
register int i, ret = 0;
register u16 cw, p;
u8 ch;
for (i = 0 ; i<n ;) {
switch (c->mode) {
case COMPRESSED:
/* get cw from input */
while (c->bit_len < c->cw_size && i<n) {
c->bit_data |= *in++ << c->bit_len;
c->bit_len += 8;
i++;
}
if (c->bit_len < c->cw_size)
break;
cw = c->bit_data&((1<<c->cw_size)-1);
c->bit_data >>= c->cw_size;
c->bit_len -= c->cw_size;
/* process cw */
switch (cw) {
/* control cw */
case CCW_ETM: /* enter transparent mode */
COMP_DBG("C decoder: ETM\n");
c->bit_len = 0;
c->mode = TRANSPARENT;
c->last_matched = 0;
c->last_added = 0;
break;
case CCW_FLUSH: /* flush signal */
COMP_DBG("C decoder: FLUSH\n");
c->bit_len = 0;
break;
case CCW_STEPUP: /* increase cw size */
c->cw_size++;
c->threshold <<= 1;
COMP_DBG("C decoder: STEPUP %d\n", c->cw_size);
if (c->cw_size > (c->dict_size>>3)) { /* C-ERROR */
COMP_ERR("fatal: cw size too big!\n");
return -1;
}
break;
default: /* regular codeword */
if (cw == c->next_free) { /* C-ERROR */
COMP_ERR("fatal: next free!\n");
return -1;
}
ret += recv_data(c,cw);
if (c->update_at) {
ch = c->str_data[0];
p = match_char(c,c->update_at,ch);
if (!p) {
// fixme: prevent case add 303 at 303
c->last_added = add_char(c,c->update_at,ch);
COMP_DBG1("C dec add %d at %d: %d(%c) '%s'\n",
c->last_added,c->update_at,
ch,ch,show_string(c,c->last_added));
if (cw == c->next_free) { /* C-ERROR */
COMP_ERR("fatal: empty cw!\n");
return -1;
}
}
else if (p == c->last_added)
c->last_added = 0;
}
if (c->str_len + c->flushed_len == c->max_string) {
c->update_at = 0;
}
else {
c->update_at = cw;
}
/* process escape */
// fixme: separate it in func (send_string ?)
{ int j;
for (j=0;j<c->str_len;j++) {
if(c->str_data[j]==c->escape_char) {
COMP_DBG("C dec: ESCAPE %d\n", c->escape_char);
c->escape_char+=ESCAPE_STEP;
}
}
}
send_string(c);
break;
}
break;
case TRANSPARENT:
if (c->escape) { /* command */
c->escape = 0;
switch(*in) {
case CC_ECM: /* enter compressed mode */
COMP_DBG("T decoder: ECM\n");
ret += send_string(c);
c->mode = COMPRESSED;
c->update_at = c->last_matched;
c->last_matched = 0;
in++;i++;
continue;
//break;
case CC_EID: /* escape symbol */
COMP_DBG("T decoder: EID\n");
// fixme: don't rewrite in buf
*in = c->escape_char;
c->escape_char += ESCAPE_STEP;
break;
case CC_RESET: /* reset dict */
COMP_DBG("T decoder: RESET\n");
// TBD
ret += send_string(c);
dict_init(c);
in++;i++;
continue;
//break;
default: /* C-ERROR */
COMP_ERR("fatal: invalid cmd!\n");
return -1;
}
}
else if (*in == c->escape_char) {
COMP_DBG("T dec ESCAPE %d\n", *in);
c->escape = 1;
in++; i++;
break;
}
//COMP_DBG1("dec rcv: %d(%c)\n", *in, *in);
if (c->update_at) {
if(!match_char(c,c->update_at,*in)) {
c->last_added = add_char(c,c->update_at,*in);
COMP_DBG1("T dec add %d at %d: %d(%c) '%s'\n",
c->last_added,c->update_at,
*in,*in,show_string(c,c->last_added));
}
c->update_at = 0;
}
cw = match_char(c,c->last_matched,*in);
if(!cw) {
c->update_at = c->last_matched;
/* out string */
ret += send_string(c);
c->last_matched = 0;
}
else if (cw == c->last_added) {
c->last_added = 0;
/* out string */
ret += send_string(c);
c->last_matched = 0;
}
else {
c->last_matched = cw;
c->str_data[c->str_len++] = *in++;
if (c->str_len + c->flushed_len == c->max_string) {
/* out string */
ret += send_string(c);
c->last_matched = 0;
}
i++;
}
break;
default:
return -1;
}
}
return ret;
}
/*
* encoder
*
*/
#define SEND_CW(c,cw) { (c)->bit_data |= (cw) << (c)->bit_len; \
(c)->bit_len += (c)->cw_size; }
#define ALIGN_BITS(c) { if((c)->bit_len%8) \
(c)->bit_len += 8 - (c)->bit_len%8; }
#define SEND_BITS(c) { while((c)->bit_len >= 8) { \
(c)->obuf[c->olen++]=(c)->bit_data&0xff; \
(c)->bit_data >>= 8; \
(c)->bit_len -= 8; ret++; \
} }
/* send encoded data */
static int send_data(struct comp_state *c, u16 cw)
{
register int i, ret = 0;
/* update test info */
c->raw_bits += c->str_len<<3;
c->cmp_bits += c->cw_size;
c->cmp_last += c->cw_size - c->cmp_last*c->str_len/TEST_SLICE;
switch(c->mode) {
case TRANSPARENT:
for (i = 0;i<c->str_len;i++) {
c->obuf[c->olen++] = c->str_data[i];
ret++;
if (c->str_data[i] == c->escape_char) {
COMP_DBG("T enc ESCAPE %d\n", c->escape_char);
c->obuf[c->olen++] = CC_EID;
ret++;
c->escape_char += ESCAPE_STEP;
}
COMP_DBG1("enc send: %d(%c)\n",c->str_data[i],c->str_data[i]);
}
break;
case COMPRESSED:
/* process escape */
for (i = 0;i<c->str_len;i++) {
if (c->str_data[i] == c->escape_char) {
COMP_DBG("C enc ESCAPE %d\n", c->escape_char);
c->escape_char += ESCAPE_STEP;
}
}
/* send codeword */
/* check cw size and send STEPUP if need */
while (cw >= c->threshold) {
SEND_CW(c,CCW_STEPUP);
SEND_BITS(c);
c->threshold <<= 1;
c->cw_size++;
COMP_DBG("C enc STEPUP %d\n",c->cw_size);
}
SEND_CW(c,cw);
SEND_BITS(c);
COMP_DBG1("enc send %d: '%s'\n",cw,show_string(c,cw));
break;
}
c->str_len = 0;
c->flushed_len = 0;
return ret;
}
/* test compression */
static int test_mode(struct comp_state *c)
{
int ret = 0;
switch(c->mode) {
case TRANSPARENT:
/* fixme: why 11 ? */
if (c->cmp_last < (TEST_SLICE<<3) - 11) {
/* switch to compressed mode */
COMP_DBG("encoder %d/%d %d: --> COMPRESSED mode.\n",
c->cmp_bits,c->raw_bits,c->cmp_last);
if (c->last_matched) {
c->update_at = c->last_matched;
ret += send_data(c,c->last_matched);
c->last_matched = 0;
}
c->obuf[c->olen++] = c->escape_char;
c->obuf[c->olen++] = CC_ECM;
ret += 2;
c->bit_data = 0;
c->mode = COMPRESSED;
}
#if 0
else if (0) { /* fixme: when RESET? */
COMP_DBG("encoder RESET\n");
}
#endif
break;
case COMPRESSED:
if (c->cmp_last > (TEST_SLICE<<3)) {
/* switch to transparent mode */
COMP_DBG("encoder %d/%d %d: --> TRANSPARENT mode.\n",
c->cmp_bits,c->raw_bits,c->cmp_last);
if (c->last_matched) {
c->update_at = c->last_matched;
ret += send_data(c,c->last_matched);
c->last_matched = 0;
}
c->last_added = 0;
SEND_CW(c,CCW_ETM);
ALIGN_BITS(c);
SEND_BITS(c);
c->mode = TRANSPARENT;
}
break;
}
return ret;
}
/* flush encoder data */
int comp_flush_encoder(struct comp_state *c)
{
int ret = 0;
//COMP_DBG1("flush_encoder...\n");
#if 1 /* fixme: use something better */
if (c->update_at) {
return 0;
}
#endif
if (c->last_matched) {
int total_len = c->flushed_len + c->str_len;
ret = send_data(c,c->last_matched);
c->flushed_len = total_len;
}
if(c->mode == COMPRESSED) {
c->update_at = c->last_matched;
c->last_matched = 0;
c->flushed_len = 0;
//if (c->bit_len > 0) {
COMP_DBG("flush_encoder: send FLUSH\n");
/* send FLUSH and align by zeros */
SEND_CW(c,CCW_FLUSH);
ALIGN_BITS(c);
SEND_BITS(c);
//}
}
if (ret)
COMP_DBG("flush_encoder %d...\n", ret);
return ret;
}
/* encoder */
#define NEW_ENCODER 1
int comp_encode(struct comp_state *c, u8 *in, int n)
{
register int i, ret=0;
register u16 cw = 0;
for(i=0;i<n;) {
if (c->update_at) {
if (!match_char(c,c->update_at,*in)) {
c->last_added = add_char(c,c->update_at,*in);
COMP_DBG1("enc add %d at %d: %d(%c) '%s'\n",
c->last_added,c->update_at,
*in,*in,show_string(c,c->last_added));
}
c->update_at = 0;
}
#if NEW_ENCODER
/* match string */
while(i<n) {
cw = match_char(c,c->last_matched,*in);
if (!cw) {
c->update_at = c->last_matched;
ret += send_data(c,c->last_matched);
c->last_matched = 0;
break;
}
if (cw == c->last_added) {
c->last_added = 0;
ret += send_data(c,c->last_matched);
c->last_matched = 0;
break;
}
c->last_matched = cw;
/* collect char */
c->str_data[c->str_len++] = *in++; i++;
if ( c->str_len + c->flushed_len == c->max_string ) {
ret += send_data(c,c->last_matched);
c->last_matched = 0;
break;
}
}
ret += test_mode(c);
}
#else
/* match char */
cw = match_char(c,c->last_matched,*in);
if (!cw) {
/* add char */
c->update_at = c->last_matched;
/* send and reset string */
ret += send_data(c,c->last_matched);
c->last_matched = 0;
}
else if (cw == c->last_added) {
c->last_added = 0;
/* send and reset string */
ret += send_data(c,c->last_matched);
c->last_matched = 0;
}
else {
c->last_matched = cw;
/* collect char */
c->str_data[c->str_len++] = *in++;
if ( c->str_len + c->flushed_len == c->max_string ) {
/* send and reset string */
ret += send_data(c,c->last_matched);
c->last_matched = 0;
}
i++;
}
}
ret += test_mode(c);
#endif
return ret;
}
#if COMP_TEST
/*
* Test encoder procedures
*
*/
/* force reset */
int comp_force_reset(struct comp_state *c)
{
int ret = 0;
if (c->mode != TRANSPARENT)
return 0;
COMP_DBG("comp_reset...\n");
if (c->last_matched) {
ret = send_data(c,c->last_matched);
c->last_matched = 0;
}
c->obuf[c->olen++] = c->escape_char;
c->obuf[c->olen++] = CC_RESET;
ret += 2;
c->bit_data = 0;
dict_init(c);
return ret;
}
/* force change compression mode */
int comp_force_change_mode(struct comp_state *c, enum COMP_MODE mode)
{
int ret = 0;
if (mode == c->mode)
return 0;
COMP_DBG("change_mode...\n");
if (c->last_matched) {
c->update_at = c->last_matched;
ret += send_data(c,c->last_matched);
c->last_matched = 0;
}
/* switch to compressed mode */
if (c->mode == TRANSPARENT) {
COMP_DBG("encoder %ld/%ld %d: switch to COMPRESSED mode.\n",
c->cmp_bits,c->raw_bits,c->cmp_last);
c->obuf[c->olen++] = c->escape_char;
c->obuf[c->olen++] = CCW_ECM;
ret += 2;
c->bit_data = 0;
c->mode = COMPRESSED;
}
/* switch to transparent mode */
else if (c->mode == COMPRESSED) {
COMP_DBG("encoder %ld/%ld %d: switch to TRANSPARENT mode.\n",
c->cmp_bits,c->raw_bits,c->cmp_last);
SEND_CW(c,CCW_ETM);
ALIGN_BITS(c);
SEND_BITS(c);
c->mode = TRANSPARENT;
}
return ret;
}
#endif
/*
* init procedures
*
*/
int comp_init(struct comp_state *c,int dict_size,int max_str)
{
struct dict *d;
int i;
memset(c,0,sizeof(*c));
if (dict_size < MIN_DICT_SIZE ||
max_str < MIN_STR_SIZE || max_str > MAX_STR_SIZE)
return -1;
/* alloc dict */
d = COMP_ALLOC(dict_size*sizeof(*d));
if (!d)
return -1;
COMP_DBG("comp_init: dict size %d, max str %d (dict %d bytes).\n",
dict_size,max_str,dict_size*sizeof(*d));
memset(d,0,dict_size*sizeof(*d));
c->max_string= max_str;
c->dict_size = dict_size;
c->dict = d;
/* init dict */
for(i=0;i<TOTAL_CHARS;i++) {
c->dict[i+FIRST_ENTRY].ch = i;
}
dict_init(c);
/* temporary */
c->olen = 0;
return 0;
}
int comp_config(struct comp_state *c,int dict_size,int max_str)
{
if (dict_size < MIN_DICT_SIZE ||
max_str < MIN_STR_SIZE || max_str > MAX_STR_SIZE)
return -1;
if (dict_size > c->dict_size || max_str > sizeof(c->str_data))
return -1;
c->max_string= max_str;
c->dict_size = dict_size;
//dict_init(c);
/* temporary */
//c->olen = 0;
return 0;
}
int comp_exit(struct comp_state *c)
{
COMP_DBG("comp exit: %d/%d %d\n",
c->cmp_bits,c->raw_bits,c->cmp_last);
c->dict_size = 0;
if(c->dict) {
COMP_FREE(c->dict);
c->dict = NULL;
}
return 0;
}
/*
* modem init procedures
*
*/
/* exported functions prototypes */
extern int comp_init(struct comp_state *c,int dict_size,int max_str);
extern int comp_config(struct comp_state *c,int dict_size,int max_str);
extern int comp_exit(struct comp_state *c);
extern int comp_encode(struct comp_state *c, u8 *in, int n);
extern int comp_decode(struct comp_state *c, u8 *in, int n);
extern int comp_flush_encoder(struct comp_state *c);
extern int comp_flush_decoder(struct comp_state *c);
int modem_comp_init(struct modem *m)
{
int ret;
ret = comp_init(&m->comp.encoder,
m->cfg.comp_dict_size,m->cfg.comp_max_string);
if(ret)
return ret;
ret = comp_init(&m->comp.decoder,
m->cfg.comp_dict_size,m->cfg.comp_max_string);
if (ret) {
comp_exit(&m->comp.encoder);
return ret;
}
return 0;
}
int modem_comp_config(struct modem *m,int dict_size,int max_str)
{
int ret = 0;
if ((ret = comp_config(&m->comp.encoder,dict_size,max_str)) ||
(ret = comp_config(&m->comp.decoder,dict_size,max_str)) )
COMP_ERR("modem_comp_config: setup failed (%d/%d).\n",
dict_size,max_str);
return ret;
}
void modem_comp_exit(struct modem *m)
{
comp_exit(&m->comp.encoder);
comp_exit(&m->comp.decoder);
// temproary
m->comp.head = m->comp.count = 0;
}
// FIXME: remove this stupest things
static int get_odata(struct comp_state *c, char *buf, int n)
{
int ret = 0;
if(c->olen > 0) {
ret = c->olen - c->ohead < n ?
c->olen - c->ohead : n;
memcpy(buf,c->obuf+c->ohead,ret);
c->ohead += ret;
if (c->ohead == c->olen) {
c->ohead = 0;
c->olen = 0;
}
}
return ret;
}
int modem_comp_encode(struct modem *m, u8 ch, char *buf, int n)
{
int ret;
ret = get_odata(&m->comp.encoder,buf,n);
if (ret == n) return ret;
if(comp_encode(&m->comp.encoder,&ch,1)<0)
return -1;
ret += get_odata(&m->comp.encoder,buf+ret,n-ret);
return ret;
}
int modem_comp_decode(struct modem *m, u8 ch, char *buf, int n)
{
int ret;
ret = get_odata(&m->comp.decoder,buf,n);
if(ret == n) return ret;
if(comp_decode(&m->comp.decoder,&ch,1)<0)
return -1;
ret += get_odata(&m->comp.decoder,buf+ret,n-ret);
return ret;
}
int modem_comp_flush_encoder(struct modem *m, char *buf, int n)
{
int ret;
ret = get_odata(&m->comp.encoder,buf,n);
if (ret == n) return ret;
if(comp_flush_encoder(&m->comp.encoder) < 0)
return -1;
ret += get_odata(&m->comp.encoder,buf+ret,n-ret);
return ret;
}
int modem_comp_flush_decoder(struct modem *m, char *buf, int n)
{
int ret;
ret = get_odata(&m->comp.decoder,buf,n);
if (ret == n) return ret;
if(comp_flush_decoder(&m->comp.decoder) < 0)
return -1;
ret += get_odata(&m->comp.decoder,buf+ret,n-ret);
return ret;
}