/* * * 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 #include /* * 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;idict_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 ; imode) { case COMPRESSED: /* get cw from input */ while (c->bit_len < c->cw_size && ibit_data |= *in++ << c->bit_len; c->bit_len += 8; i++; } if (c->bit_len < c->cw_size) break; cw = c->bit_data&((1<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;jstr_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;istr_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;istr_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;iupdate_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(ilast_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;idict[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; }