/* * ccp_pred1.c * * Rewritten by Alexander Motin * Written by Archie Cobbs * Copyright (c) 1995-1999 Whistle Communications, Inc. All rights reserved. * See ``COPYRIGHT.whistle'' */ /* * pred1.c * * Test program for Dave Rand's rendition of the predictor algorithm * * Updated by: archie@freebsd.org (Archie Cobbs) * Updated by: iand@labtam.labtam.oz.au (Ian Donaldson) * Updated by: Carsten Bormann * Original : Dave Rand / */ #include "ppp.h" #include "ccp.h" #include "util.h" #include "ngfunc.h" #ifdef USE_NG_PRED1 #include #include #endif /* * DEFINITIONS */ #define PRED1_COMP_BUF_SIZE 2048 #define PRED1_DECOMP_BUF_SIZE 1600 #define PRED1_MAX_BLOWUP(n) ((n) * 9 / 8 + 24) /* * The following hash code is the heart of the algorithm: * It builds a sliding hash sum of the previous 3-and-a-bit characters * which will be used to index the guess table. * A better hash function would result in additional compression, * at the expense of time. */ #define IHASH(x) p->iHash = (p->iHash << 4) ^ (x) #define OHASH(x) p->oHash = (p->oHash << 4) ^ (x) /* * INTERNAL FUNCTIONS */ static int Pred1Init(int direction); static void Pred1Cleanup(int direction); #ifndef USE_NG_PRED1 static Mbuf Pred1Compress(Mbuf plain); static Mbuf Pred1Decompress(Mbuf comp); #endif static u_char *Pred1BuildConfigReq(u_char *cp, int *ok); static void Pred1DecodeConfigReq(Fsm fp, FsmOption opt, int mode); static Mbuf Pred1RecvResetReq(int id, Mbuf bp, int *noAck); static Mbuf Pred1SendResetReq(void); static void Pred1RecvResetAck(int id, Mbuf bp); static int Pred1Negotiated(int xmit); static int Pred1SubtractBloat(int size); static int Pred1Stat(int dir); #ifndef USE_NG_PRED1 static int Compress(u_char *source, u_char *dest, int len); static int Decompress(u_char *source, u_char *dest, int slen, int dlen); static void SyncTable(u_char *source, u_char *dest, int len); #endif /* * GLOBAL VARIABLES */ const struct comptype gCompPred1Info = { "pred1", CCP_TY_PRED1, 1, Pred1Init, NULL, NULL, NULL, Pred1SubtractBloat, Pred1Cleanup, Pred1BuildConfigReq, Pred1DecodeConfigReq, Pred1SendResetReq, Pred1RecvResetReq, Pred1RecvResetAck, Pred1Negotiated, Pred1Stat, #ifndef USE_NG_PRED1 Pred1Compress, Pred1Decompress, #else NULL, NULL, #endif }; /* * Pred1Init() */ static int Pred1Init(int directions) { #ifndef USE_NG_PRED1 Pred1Info p = &bund->ccp.pred1; if (directions == COMP_DIR_RECV) { p->iHash = 0; if (p->InputGuessTable == NULL) p->InputGuessTable = Malloc(MB_COMP, PRED1_TABLE_SIZE); memset(p->InputGuessTable, 0, PRED1_TABLE_SIZE); } if (directions == COMP_DIR_XMIT) { p->oHash = 0; if (p->OutputGuessTable == NULL) p->OutputGuessTable = Malloc(MB_COMP, PRED1_TABLE_SIZE); memset(p->OutputGuessTable, 0, PRED1_TABLE_SIZE); } #else struct ngm_mkpeer mp; struct ng_pred1_config conf; const char *pred1hook, *ppphook; char path[NG_PATHLEN + 1]; memset(&conf, 0, sizeof(conf)); conf.enable = 1; switch (directions) { case COMP_DIR_XMIT: ppphook = NG_PPP_HOOK_COMPRESS; pred1hook = NG_PRED1_HOOK_COMP; break; case COMP_DIR_RECV: ppphook = NG_PPP_HOOK_DECOMPRESS; pred1hook = NG_PRED1_HOOK_DECOMP; break; default: assert(0); return(-1); } /* Attach a new PRED1 node to the PPP node */ snprintf(mp.type, sizeof(mp.type), "%s", NG_PRED1_NODE_TYPE); snprintf(mp.ourhook, sizeof(mp.ourhook), "%s", ppphook); snprintf(mp.peerhook, sizeof(mp.peerhook), "%s", pred1hook); if (NgSendMsg(bund->csock, MPD_HOOK_PPP, NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) { Log(LG_ERR, ("[%s] can't create %s node: %s", bund->name, mp.type, strerror(errno))); return(-1); } /* Configure PRED1 node */ snprintf(path, sizeof(path), "%s.%s", MPD_HOOK_PPP, ppphook); if (NgSendMsg(bund->csock, path, NGM_PRED1_COOKIE, NGM_PRED1_CONFIG, &conf, sizeof(conf)) < 0) { Log(LG_ERR, ("[%s] can't config %s node at %s: %s", bund->name, NG_PRED1_NODE_TYPE, path, strerror(errno))); NgFuncDisconnect(MPD_HOOK_PPP, ppphook); return(-1); } #endif return 0; } /* * Pred1Cleanup() */ void Pred1Cleanup(int dir) { #ifndef USE_NG_PRED1 Pred1Info p = &bund->ccp.pred1; if (dir == COMP_DIR_RECV) { assert(p->InputGuessTable); Freee(MB_COMP, p->InputGuessTable); p->InputGuessTable = NULL; memset(&p->recv_stats, 0, sizeof(p->recv_stats)); } if (dir == COMP_DIR_XMIT) { assert(p->OutputGuessTable); Freee(MB_COMP, p->OutputGuessTable); p->OutputGuessTable = NULL; memset(&p->xmit_stats, 0, sizeof(p->xmit_stats)); } #else const char *ppphook; char path[NG_PATHLEN + 1]; /* Remove node */ switch (dir) { case COMP_DIR_XMIT: ppphook = NG_PPP_HOOK_COMPRESS; break; case COMP_DIR_RECV: ppphook = NG_PPP_HOOK_DECOMPRESS; break; default: assert(0); return; } snprintf(path, sizeof(path), "%s.%s", MPD_HOOK_PPP, ppphook); (void)NgFuncShutdownNode(bund, bund->name, path); #endif } #ifndef USE_NG_PRED1 /* * Pred1Compress() * * Compress a packet and return a compressed version. * The original is untouched. */ Mbuf Pred1Compress(Mbuf plain) { u_char *wp, *uncomp, *comp; u_int16_t fcs; int len; Mbuf res; int orglen; Pred1Info p = &bund->ccp.pred1; plain = mbunify(plain); orglen = plength(plain); uncomp = MBDATA(plain); p->xmit_stats.InOctets += orglen; p->xmit_stats.FramesPlain++; res = mballoc(MB_COMP, PRED1_MAX_BLOWUP(orglen + 2)); comp = MBDATA(res); wp = comp; *wp++ = (orglen >> 8) & 0x7F; *wp++ = orglen & 0xFF; /* Compute FCS */ fcs = Crc16(PPP_INITFCS, comp, 2); fcs = Crc16(fcs, uncomp, orglen); fcs = ~fcs; /* Compress data */ len = Compress(uncomp, wp, orglen); /* What happened? */ if (len < orglen) { *comp |= 0x80; wp += len; p->xmit_stats.FramesComp++; } else { memcpy(wp, uncomp, orglen); wp += orglen; p->xmit_stats.FramesUncomp++; } /* Add FCS */ *wp++ = fcs & 0xFF; *wp++ = fcs >> 8; res->cnt = (wp - comp); PFREE(plain); Log(LG_CCP2, ("[%s] Pred1: orig (%d) --> comp (%d)", bund->name, orglen, res->cnt)); p->xmit_stats.OutOctets += res->cnt; return res; } /* * Pred1Decompress() * * Decompress a packet and return a compressed version. * The original is untouched. */ Mbuf Pred1Decompress(Mbuf mbcomp) { u_char *uncomp, *comp; u_char *cp; u_int16_t len, len1, cf, lenn; u_int16_t fcs; int orglen; Mbuf mbuncomp; Pred1Info p = &bund->ccp.pred1; mbcomp = mbunify(mbcomp); orglen = plength(mbcomp); comp = MBDATA(mbcomp); cp = comp; p->recv_stats.InOctets += orglen; mbuncomp = mballoc(MB_COMP, PRED1_DECOMP_BUF_SIZE); uncomp = MBDATA(mbuncomp); /* Get initial length value */ len = *cp++ << 8; len += *cp++; cf = (len & 0x8000); len &= 0x7fff; /* Is data compressed or not really? */ if (cf) { p->recv_stats.FramesComp++; len1 = Decompress(cp, uncomp, orglen - 4, PRED1_DECOMP_BUF_SIZE); if (len != len1) /* Error is detected. Send reset request */ { Log(LG_CCP2, ("[%s] Length error (%d) --> len (%d)", bund->name, len, len1)); p->recv_stats.Errors++; PFREE(mbcomp); PFREE(mbuncomp); CcpSendResetReq(); return NULL; } cp += orglen - 4; } else { p->recv_stats.FramesUncomp++; SyncTable(cp, uncomp, len); cp += len; } mbuncomp->cnt = len; /* Check CRC */ lenn = htons(len); fcs = Crc16(PPP_INITFCS, (u_char *)&lenn, 2); fcs = Crc16(fcs, uncomp, len); fcs = Crc16(fcs, cp, 2); #ifdef DEBUG if (fcs != PPP_GOODFCS) Log(LG_CCP2, ("fcs = %04x (%s), len = %x, olen = %x", fcs, (fcs == PPP_GOODFCS)? "good" : "bad", len, orglen)); #endif if (fcs != PPP_GOODFCS) { Log(LG_CCP2, ("[%s] Pred1: Bad CRC-16", bund->name)); p->recv_stats.Errors++; PFREE(mbcomp); PFREE(mbuncomp); CcpSendResetReq(); return NULL; } Log(LG_CCP2, ("[%s] Pred1: orig (%d) <-- comp (%d)", bund->name, mbuncomp->cnt, orglen)); PFREE(mbcomp); p->recv_stats.FramesPlain++; p->recv_stats.OutOctets += mbuncomp->cnt; return mbuncomp; } #endif /* * Pred1RecvResetReq() */ static Mbuf Pred1RecvResetReq(int id, Mbuf bp, int *noAck) { #ifndef USE_NG_PRED1 Pred1Info p = &bund->ccp.pred1; Pred1Init(COMP_DIR_XMIT); p->xmit_stats.Errors++; #else char path[NG_PATHLEN + 1]; /* Forward ResetReq to the DEFLATE compression node */ snprintf(path, sizeof(path), "%s.%s", MPD_HOOK_PPP, NG_PPP_HOOK_COMPRESS); if (NgSendMsg(bund->csock, path, NGM_PRED1_COOKIE, NGM_PRED1_RESETREQ, NULL, 0) < 0) { Log(LG_ERR, ("[%s] reset to %s node: %s", bund->name, NG_PRED1_NODE_TYPE, strerror(errno))); } #endif return(NULL); } /* * Pred1SendResetReq() */ static Mbuf Pred1SendResetReq(void) { #ifndef USE_NG_PRED1 Pred1Init(COMP_DIR_RECV); #endif return(NULL); } /* * Pred1RecvResetAck() */ static void Pred1RecvResetAck(int id, Mbuf bp) { #ifndef USE_NG_PRED1 Pred1Init(COMP_DIR_RECV); #else char path[NG_PATHLEN + 1]; /* Forward ResetReq to the DEFLATE compression node */ snprintf(path, sizeof(path), "%s.%s", MPD_HOOK_PPP, NG_PPP_HOOK_DECOMPRESS); if (NgSendMsg(bund->csock, path, NGM_PRED1_COOKIE, NGM_PRED1_RESETREQ, NULL, 0) < 0) { Log(LG_ERR, ("[%s] reset to %s node: %s", bund->name, NG_PRED1_NODE_TYPE, strerror(errno))); } #endif } /* * Pred1BuildConfigReq() */ static u_char * Pred1BuildConfigReq(u_char *cp, int *ok) { cp = FsmConfValue(cp, CCP_TY_PRED1, 0, NULL); *ok = 1; return (cp); } /* * Pred1DecodeConfigReq() */ static void Pred1DecodeConfigReq(Fsm fp, FsmOption opt, int mode) { /* Deal with it */ switch (mode) { case MODE_REQ: FsmAck(fp, opt); break; case MODE_NAK: break; } } /* * Pred1Negotiated() */ static int Pred1Negotiated(int dir) { return 1; } /* * Pred1SubtractBloat() */ static int Pred1SubtractBloat(int size) { return(size - 4); } static int Pred1Stat(int dir) { #ifndef USE_NG_PRED1 Pred1Info p = &bund->ccp.pred1; switch (dir) { case COMP_DIR_XMIT: Printf("\tBytes\t: %llu -> %llu (%+lld%%)\r\n", p->xmit_stats.InOctets, p->xmit_stats.OutOctets, ((p->xmit_stats.InOctets!=0)? ((int64_t)(p->xmit_stats.OutOctets - p->xmit_stats.InOctets)*100/(int64_t)p->xmit_stats.InOctets): 0)); Printf("\tFrames\t: %llu -> %lluc + %lluu\r\n", p->xmit_stats.FramesPlain, p->xmit_stats.FramesComp, p->xmit_stats.FramesUncomp); Printf("\tErrors\t: %llu\r\n", p->recv_stats.Errors); break; case COMP_DIR_RECV: Printf("\tBytes\t: %llu <- %llu (%+lld%%)\r\n", p->recv_stats.OutOctets, p->recv_stats.InOctets, ((p->recv_stats.OutOctets!=0)? ((int64_t)(p->recv_stats.InOctets - p->recv_stats.OutOctets)*100/(int64_t)p->recv_stats.OutOctets): 0)); Printf("\tFrames\t: %llu <- %lluc + %lluu\r\n", p->xmit_stats.FramesPlain, p->xmit_stats.FramesComp, p->xmit_stats.FramesUncomp); Printf("\tErrors\t: %llu\r\n", p->recv_stats.Errors); break; default: assert(0); } return (0); #else char path[NG_PATHLEN + 1]; struct ng_pred1_stats stats; union { u_char buf[sizeof(struct ng_mesg) + sizeof(stats)]; struct ng_mesg reply; } u; switch (dir) { case COMP_DIR_XMIT: snprintf(path, sizeof(path), "mpd%d-%s:%s", gPid, bund->name, NG_PPP_HOOK_COMPRESS); break; case COMP_DIR_RECV: snprintf(path, sizeof(path), "mpd%d-%s:%s", gPid, bund->name, NG_PPP_HOOK_DECOMPRESS); break; default: assert(0); } if (NgFuncSendQuery(path, NGM_PRED1_COOKIE, NGM_PRED1_GET_STATS, NULL, 0, &u.reply, sizeof(u), NULL) < 0) { Log(LG_ERR, ("[%s] can't get %s stats: %s", bund->name, NG_BPF_NODE_TYPE, strerror(errno))); return(0); } memcpy(&stats, u.reply.data, sizeof(stats)); switch (dir) { case COMP_DIR_XMIT: Printf("\tBytes\t: %llu -> %llu (%+lld%%)\r\n", stats.InOctets, stats.OutOctets, ((stats.InOctets!=0)? ((int64_t)(stats.OutOctets - stats.InOctets)*100/(int64_t)stats.InOctets): 0)); Printf("\tFrames\t: %llu -> %lluc + %lluu\r\n", stats.FramesPlain, stats.FramesComp, stats.FramesUncomp); Printf("\tErrors\t: %llu\r\n", stats.Errors); break; case COMP_DIR_RECV: Printf("\tBytes\t: %llu <- %llu (%+lld%%)\r\n", stats.OutOctets, stats.InOctets, ((stats.OutOctets!=0)? ((int64_t)(stats.InOctets - stats.OutOctets)*100/(int64_t)stats.OutOctets): 0)); Printf("\tFrames\t: %llu <- %lluc + %lluu\r\n", stats.FramesPlain, stats.FramesComp, stats.FramesUncomp); Printf("\tErrors\t: %llu\r\n", stats.Errors); break; default: assert(0); } return (0); #endif } #ifndef USE_NG_PRED1 /* * Compress() */ static int Compress(u_char *source, u_char *dest, int len) { Pred1Info p = &bund->ccp.pred1; int i, bitmask; u_char flags; u_char *flagdest, *orgdest; orgdest = dest; while (len) { flagdest = dest++; flags = 0; /* All guess wrong initially */ for (bitmask=1, i=0; i < 8 && len; i++, bitmask <<= 1) { if (p->OutputGuessTable[p->oHash] == *source) flags |= bitmask; /* Guess was right - don't output */ else { p->OutputGuessTable[p->oHash] = *source; *dest++ = *source; /* Guess wrong, output char */ } OHASH(*source++); len--; } *flagdest = flags; } return(dest - orgdest); } /* * Decompress() * * Returns decompressed size, or -1 if we ran out of space */ static int Decompress(u_char *source, u_char *dest, int slen, int dlen) { Pred1Info p = &bund->ccp.pred1; int i, bitmask; u_char flags, *orgdest; orgdest = dest; while (slen) { flags = *source++; slen--; for (i=0, bitmask = 1; i < 8; i++, bitmask <<= 1) { if (dlen <= 0) return(-1); if (flags & bitmask) *dest = p->InputGuessTable[p->iHash]; /* Guess correct */ else { if (!slen) break; /* we seem to be really done -- cabo */ p->InputGuessTable[p->iHash] = *source; /* Guess wrong */ *dest = *source++; /* Read from source */ slen--; } IHASH(*dest++); dlen--; } } return(dest - orgdest); } /* * SyncTable() */ static void SyncTable(u_char *source, u_char *dest, int len) { Pred1Info p = &bund->ccp.pred1; while (len--) { if (p->InputGuessTable[p->iHash] != *source) p->InputGuessTable[p->iHash] = *source; IHASH(*dest++ = *source++); } } #endif