/*
* ccp_pred1.c
*
* Rewritten by Alexander Motin <mav@FreeBSD.org>
* Written by Archie Cobbs <archie@freebsd.org>
* 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 <cabo@cs.tu-berlin.de>
* Original : Dave Rand <dlr@bungi.com>/<dave_rand@novell.com>
*/
#include "ppp.h"
#include "ccp.h"
#include "util.h"
#include "ngfunc.h"
#ifdef USE_NG_PRED1
#include <netgraph/ng_message.h>
#include <netgraph.h>
#endif
/*
* DEFINITIONS
*/
#define PRED1_DECOMP_BUF_SIZE 4096
#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(Bund b, int direction);
static void Pred1Cleanup(Bund b, int direction);
#ifndef USE_NG_PRED1
static Mbuf Pred1Compress(Bund b, Mbuf plain);
static Mbuf Pred1Decompress(Bund b, Mbuf comp);
#endif
static u_char *Pred1BuildConfigReq(Bund b, u_char *cp, int *ok);
static void Pred1DecodeConfigReq(Fsm fp, FsmOption opt, int mode);
static Mbuf Pred1RecvResetReq(Bund b, int id, Mbuf bp, int *noAck);
static Mbuf Pred1SendResetReq(Bund b);
static void Pred1RecvResetAck(Bund b, int id, Mbuf bp);
static int Pred1Negotiated(Bund b, int xmit);
static int Pred1SubtractBloat(Bund b, int size);
static int Pred1Stat(Context ctx, int dir);
#ifndef USE_NG_PRED1
static int Compress(Bund b, u_char *source, u_char *dest, int len);
static int Decompress(Bund b, u_char *source, u_char *dest, int slen, int dlen);
static void SyncTable(Bund b, 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(Bund b, int dir)
{
#ifndef USE_NG_PRED1
Pred1Info p = &b->ccp.pred1;
if (dir == COMP_DIR_XMIT) {
p->oHash = 0;
p->OutputGuessTable = Malloc(MB_COMP, PRED1_TABLE_SIZE);
} else {
p->iHash = 0;
p->InputGuessTable = Malloc(MB_COMP, PRED1_TABLE_SIZE);
}
#else
struct ngm_mkpeer mp;
struct ng_pred1_config conf;
const char *pred1hook, *ppphook;
char path[NG_PATHSIZ];
ng_ID_t id;
memset(&conf, 0, sizeof(conf));
conf.enable = 1;
if (dir == COMP_DIR_XMIT) {
ppphook = NG_PPP_HOOK_COMPRESS;
pred1hook = NG_PRED1_HOOK_COMP;
} else {
ppphook = NG_PPP_HOOK_DECOMPRESS;
pred1hook = NG_PRED1_HOOK_DECOMP;
}
/* Attach a new PRED1 node to the PPP node */
snprintf(path, sizeof(path), "[%x]:", b->nodeID);
strcpy(mp.type, NG_PRED1_NODE_TYPE);
strcpy(mp.ourhook, ppphook);
strcpy(mp.peerhook, pred1hook);
if (NgSendMsg(gCcpCsock, path,
NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) {
Perror("[%s] can't create %s node", b->name, mp.type);
return(-1);
}
strlcat(path, ppphook, sizeof(path));
if ((id = NgGetNodeID(-1, path)) == 0) {
Perror("[%s] Cannot get %s node id", b->name, NG_PRED1_NODE_TYPE);
goto fail;
}
if (dir == COMP_DIR_XMIT) {
b->ccp.comp_node_id = id;
} else {
b->ccp.decomp_node_id = id;
}
/* Configure PRED1 node */
snprintf(path, sizeof(path), "[%x]:", id);
if (NgSendMsg(gCcpCsock, path,
NGM_PRED1_COOKIE, NGM_PRED1_CONFIG, &conf, sizeof(conf)) < 0) {
Perror("[%s] can't config %s node at %s",
b->name, NG_PRED1_NODE_TYPE, path);
goto fail;
}
#endif
return 0;
fail:
NgFuncShutdownNode(gCcpCsock, b->name, path);
return(-1);
}
/*
* Pred1Cleanup()
*/
void
Pred1Cleanup(Bund b, int dir)
{
#ifndef USE_NG_PRED1
Pred1Info p = &b->ccp.pred1;
if (dir == COMP_DIR_XMIT) {
assert(p->OutputGuessTable);
Freee(p->OutputGuessTable);
p->OutputGuessTable = NULL;
memset(&p->xmit_stats, 0, sizeof(p->xmit_stats));
} else {
assert(p->InputGuessTable);
Freee(p->InputGuessTable);
p->InputGuessTable = NULL;
memset(&p->recv_stats, 0, sizeof(p->recv_stats));
}
#else
char path[NG_PATHSIZ];
/* Remove node */
if (dir == COMP_DIR_XMIT) {
snprintf(path, sizeof(path), "[%x]:", b->ccp.comp_node_id);
b->ccp.comp_node_id = 0;
} else {
snprintf(path, sizeof(path), "[%x]:", b->ccp.decomp_node_id);
b->ccp.decomp_node_id = 0;
}
NgFuncShutdownNode(gCcpCsock, b->name, path);
#endif
}
#ifndef USE_NG_PRED1
/*
* Pred1Compress()
*
* Compress a packet and return a compressed version.
* The original is untouched.
*/
Mbuf
Pred1Compress(Bund b, Mbuf plain)
{
u_char *wp, *uncomp, *comp;
u_int16_t fcs;
int len;
Mbuf res;
int orglen;
Pred1Info p = &b->ccp.pred1;
orglen = MBLEN(plain);
uncomp = MBDATA(plain);
p->xmit_stats.InOctets += orglen;
p->xmit_stats.FramesPlain++;
res = mballoc(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(b, 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);
mbfree(plain);
Log(LG_CCP2, ("[%s] Pred1: orig (%d) --> comp (%d)", b->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(Bund b, 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 = &b->ccp.pred1;
orglen = MBLEN(mbcomp);
comp = MBDATA(mbcomp);
cp = comp;
p->recv_stats.InOctets += orglen;
mbuncomp = mballoc(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(b, 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)", b->name, len, len1));
p->recv_stats.Errors++;
mbfree(mbcomp);
mbfree(mbuncomp);
CcpSendResetReq(b);
return NULL;
}
cp += orglen - 4;
}
else
{
p->recv_stats.FramesUncomp++;
SyncTable(b, 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", b->name));
p->recv_stats.Errors++;
mbfree(mbcomp);
mbfree(mbuncomp);
CcpSendResetReq(b);
return NULL;
}
Log(LG_CCP2, ("[%s] Pred1: orig (%d) <-- comp (%d)", b->name, mbuncomp->cnt, orglen));
mbfree(mbcomp);
p->recv_stats.FramesPlain++;
p->recv_stats.OutOctets += mbuncomp->cnt;
return mbuncomp;
}
#endif
/*
* Pred1RecvResetReq()
*/
static Mbuf
Pred1RecvResetReq(Bund b, int id, Mbuf bp, int *noAck)
{
(void)id;
(void)bp;
(void)noAck;
#ifndef USE_NG_PRED1
Pred1Info p = &b->ccp.pred1;
(void)id;
(void)bp;
(void)noAck;
Pred1Init(b, COMP_DIR_XMIT);
p->xmit_stats.Errors++;
#else
char path[NG_PATHSIZ];
(void)id;
(void)bp;
(void)noAck;
/* Forward ResetReq to the Predictor1 compression node */
snprintf(path, sizeof(path), "[%x]:", b->ccp.comp_node_id);
if (NgSendMsg(gCcpCsock, path,
NGM_PRED1_COOKIE, NGM_PRED1_RESETREQ, NULL, 0) < 0) {
Perror("[%s] reset to %s node", b->name, NG_PRED1_NODE_TYPE);
}
#endif
return(NULL);
}
/*
* Pred1SendResetReq()
*/
static Mbuf
Pred1SendResetReq(Bund b)
{
#ifndef USE_NG_PRED1
Pred1Init(b, COMP_DIR_RECV);
#else
(void)b;
#endif
return(NULL);
}
/*
* Pred1RecvResetAck()
*/
static void
Pred1RecvResetAck(Bund b, int id, Mbuf bp)
{
#ifndef USE_NG_PRED1
(void)id;
(void)bp;
Pred1Init(b, COMP_DIR_RECV);
#else
char path[NG_PATHSIZ];
(void)id;
(void)bp;
/* Forward ResetReq to the Predictor1 decompression node */
snprintf(path, sizeof(path), "[%x]:", b->ccp.decomp_node_id);
if (NgSendMsg(gCcpCsock, path,
NGM_PRED1_COOKIE, NGM_PRED1_RESETREQ, NULL, 0) < 0) {
Perror("[%s] reset to %s node", b->name, NG_PRED1_NODE_TYPE);
}
#endif
}
/*
* Pred1BuildConfigReq()
*/
static u_char *
Pred1BuildConfigReq(Bund b, u_char *cp, int *ok)
{
(void)b;
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(Bund b, int dir)
{
(void)b;
(void)dir;
return 1;
}
/*
* Pred1SubtractBloat()
*/
static int
Pred1SubtractBloat(Bund b, int size)
{
(void)b;
return(size - 4);
}
static int
Pred1Stat(Context ctx, int dir)
{
#ifndef USE_NG_PRED1
Pred1Info p = &ctx->bund->ccp.pred1;
switch (dir) {
case COMP_DIR_XMIT:
Printf("\tBytes\t: %llu -> %llu (%+lld%%)\r\n",
(unsigned long long)p->xmit_stats.InOctets,
(unsigned long long)p->xmit_stats.OutOctets,
((p->xmit_stats.InOctets!=0)?
((long long)(p->xmit_stats.OutOctets - p->xmit_stats.InOctets)
*100/(long long)p->xmit_stats.InOctets):
0));
Printf("\tFrames\t: %llu -> %lluc + %lluu\r\n",
(unsigned long long)p->xmit_stats.FramesPlain,
(unsigned long long)p->xmit_stats.FramesComp,
(unsigned long long)p->xmit_stats.FramesUncomp);
Printf("\tErrors\t: %llu\r\n",
(unsigned long long)p->recv_stats.Errors);
break;
case COMP_DIR_RECV:
Printf("\tBytes\t: %llu <- %llu (%+lld%%)\r\n",
(unsigned long long)p->recv_stats.OutOctets,
(unsigned long long)p->recv_stats.InOctets,
((p->recv_stats.OutOctets!=0)?
((long long)(p->recv_stats.InOctets - p->recv_stats.OutOctets)
*100/(long long)p->recv_stats.OutOctets):
0));
Printf("\tFrames\t: %llu <- %lluc + %lluu\r\n",
(unsigned long long)p->xmit_stats.FramesPlain,
(unsigned long long)p->xmit_stats.FramesComp,
(unsigned long long)p->xmit_stats.FramesUncomp);
Printf("\tErrors\t: %llu\r\n",
(unsigned long long)p->recv_stats.Errors);
break;
default:
assert(0);
}
return (0);
#else
Bund b = ctx->bund;
char path[NG_PATHSIZ];
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, b->name,
NG_PPP_HOOK_COMPRESS);
break;
case COMP_DIR_RECV:
snprintf(path, sizeof(path), "mpd%d-%s:%s", gPid, b->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) {
Perror("[%s] can't get %s stats", b->name, NG_PRED1_NODE_TYPE);
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(Bund b, u_char *source, u_char *dest, int len)
{
Pred1Info p = &b->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(Bund b, u_char *source, u_char *dest, int slen, int dlen)
{
Pred1Info p = &b->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(Bund b, u_char *source, u_char *dest, int len)
{
Pred1Info p = &b->ccp.pred1;
while (len--)
{
if (p->InputGuessTable[p->iHash] != *source)
p->InputGuessTable[p->iHash] = *source;
IHASH(*dest++ = *source++);
}
}
#endif
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>