038a366695
We must call inflate again in case there's any pending output despite our input buffer being empty. If the output buffer is in fact already flushed, inflate() returns Z_BUF_ERROR. There isn't really an error !
580 lines
14 KiB
C
580 lines
14 KiB
C
/*
|
|
* $Id: deflate.c,v 1.2 1997/12/03 23:27:57 brian Exp $
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <netinet/in.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <zlib.h>
|
|
|
|
#include "command.h"
|
|
#include "mbuf.h"
|
|
#include "log.h"
|
|
#include "defs.h"
|
|
#include "loadalias.h"
|
|
#include "vars.h"
|
|
#include "hdlc.h"
|
|
#include "lcp.h"
|
|
#include "ccp.h"
|
|
#include "lcpproto.h"
|
|
#include "timer.h"
|
|
#include "fsm.h"
|
|
#include "deflate.h"
|
|
|
|
/* Our state */
|
|
struct deflate_state {
|
|
u_short seqno;
|
|
z_stream cx;
|
|
};
|
|
|
|
static int iWindowSize = 15;
|
|
static int oWindowSize = 15;
|
|
static struct deflate_state InputState, OutputState;
|
|
static char garbage[10];
|
|
static u_char EMPTY_BLOCK[4] = { 0x00, 0x00, 0xff, 0xff };
|
|
|
|
#define DEFLATE_CHUNK_LEN 1024 /* Allocate mbufs this size */
|
|
|
|
static void
|
|
DeflateResetOutput(void)
|
|
{
|
|
OutputState.seqno = 0;
|
|
deflateReset(&OutputState.cx);
|
|
LogPrintf(LogCCP, "Deflate: Output channel reset\n");
|
|
}
|
|
|
|
static int
|
|
DeflateOutput(int pri, u_short proto, struct mbuf *mp)
|
|
{
|
|
u_char *wp, *rp;
|
|
int olen, ilen, len, res, flush;
|
|
struct mbuf *mo_head, *mo, *mi_head, *mi;
|
|
|
|
ilen = plength(mp);
|
|
LogPrintf(LogDEBUG, "DeflateOutput: Proto %02x (%d bytes)\n", proto, ilen);
|
|
LogDumpBp(LogDEBUG, "DeflateOutput: Compress packet:", mp);
|
|
|
|
/* Stuff the protocol in front of the input */
|
|
mi_head = mi = mballoc(2, MB_HDLCOUT);
|
|
mi->next = mp;
|
|
rp = MBUF_CTOP(mi);
|
|
if (proto < 0x100) { /* Compress the protocol */
|
|
rp[0] = proto & 0377;
|
|
mi->cnt = 1;
|
|
} else { /* Don't compress the protocol */
|
|
rp[0] = proto >> 8;
|
|
rp[1] = proto & 0377;
|
|
mi->cnt = 2;
|
|
}
|
|
|
|
/* Allocate the initial output mbuf */
|
|
mo_head = mo = mballoc(DEFLATE_CHUNK_LEN, MB_HDLCOUT);
|
|
mo->cnt = 2;
|
|
wp = MBUF_CTOP(mo);
|
|
*wp++ = OutputState.seqno >> 8;
|
|
*wp++ = OutputState.seqno & 0377;
|
|
LogPrintf(LogDEBUG, "DeflateOutput: Seq %d\n", OutputState.seqno);
|
|
OutputState.seqno++;
|
|
|
|
/* Set up the deflation context */
|
|
OutputState.cx.next_out = wp;
|
|
OutputState.cx.avail_out = DEFLATE_CHUNK_LEN - 2;
|
|
OutputState.cx.next_in = MBUF_CTOP(mi);
|
|
OutputState.cx.avail_in = mi->cnt;
|
|
flush = Z_NO_FLUSH;
|
|
|
|
olen = 0;
|
|
while (1) {
|
|
if ((res = deflate(&OutputState.cx, flush)) != Z_OK) {
|
|
if (res == Z_STREAM_END)
|
|
break; /* Done */
|
|
LogPrintf(LogERROR, "DeflateOutput: deflate returned %d (%s)\n",
|
|
res, OutputState.cx.msg ? OutputState.cx.msg : "");
|
|
pfree(mo_head);
|
|
mbfree(mi_head);
|
|
OutputState.seqno--;
|
|
return 1; /* packet dropped */
|
|
}
|
|
|
|
if (flush == Z_SYNC_FLUSH && OutputState.cx.avail_out != 0)
|
|
break;
|
|
|
|
if (OutputState.cx.avail_in == 0 && mi->next != NULL) {
|
|
mi = mi->next;
|
|
OutputState.cx.next_in = MBUF_CTOP(mi);
|
|
OutputState.cx.avail_in = mi->cnt;
|
|
if (mi->next == NULL)
|
|
flush = Z_SYNC_FLUSH;
|
|
}
|
|
|
|
if (OutputState.cx.avail_out == 0) {
|
|
mo->next = mballoc(DEFLATE_CHUNK_LEN, MB_HDLCOUT);
|
|
olen += (mo->cnt = DEFLATE_CHUNK_LEN);
|
|
mo = mo->next;
|
|
mo->cnt = 0;
|
|
OutputState.cx.next_out = MBUF_CTOP(mo);
|
|
OutputState.cx.avail_out = DEFLATE_CHUNK_LEN;
|
|
}
|
|
}
|
|
|
|
olen += (mo->cnt = DEFLATE_CHUNK_LEN - OutputState.cx.avail_out);
|
|
olen -= 4; /* exclude the trailing EMPTY_BLOCK */
|
|
|
|
/*
|
|
* If the output packet (including seqno and excluding the EMPTY_BLOCK)
|
|
* got bigger, send the original - returning 0 to HdlcOutput() will
|
|
* continue to send ``mp''.
|
|
*/
|
|
if (olen >= ilen) {
|
|
pfree(mo_head);
|
|
mbfree(mi_head);
|
|
LogPrintf(LogDEBUG, "DeflateOutput: %d => %d: Uncompressible (0x%04x)\n",
|
|
ilen, olen, proto);
|
|
CcpInfo.uncompout += ilen;
|
|
CcpInfo.compout += ilen; /* We measure this stuff too */
|
|
return 0;
|
|
}
|
|
|
|
pfree(mi_head);
|
|
|
|
/*
|
|
* Lose the last four bytes of our output.
|
|
* XXX: We should probably assert that these are the same as the
|
|
* contents of EMPTY_BLOCK.
|
|
*/
|
|
for (mo = mo_head, len = mo->cnt; len < olen; mo = mo->next, len += mo->cnt)
|
|
;
|
|
mo->cnt -= len - olen;
|
|
if (mo->next != NULL) {
|
|
pfree(mo->next);
|
|
mo->next = NULL;
|
|
}
|
|
|
|
CcpInfo.uncompout += ilen;
|
|
CcpInfo.compout += olen;
|
|
|
|
LogPrintf(LogDEBUG, "DeflateOutput: %d => %d bytes, proto 0x%04x\n",
|
|
ilen, olen, proto);
|
|
|
|
HdlcOutput(PRI_NORMAL, PROTO_COMPD, mo_head);
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
DeflateResetInput(void)
|
|
{
|
|
InputState.seqno = 0;
|
|
inflateReset(&InputState.cx);
|
|
LogPrintf(LogCCP, "Deflate: Input channel reset\n");
|
|
}
|
|
|
|
static struct mbuf *
|
|
DeflateInput(u_short *proto, struct mbuf *mi)
|
|
{
|
|
struct mbuf *mo, *mo_head, *mi_head;
|
|
u_char *wp;
|
|
int ilen, olen;
|
|
int seq, flush, res, first;
|
|
u_char hdr[2];
|
|
|
|
LogDumpBp(LogDEBUG, "DeflateInput: Decompress packet:", mi);
|
|
mi_head = mi = mbread(mi, hdr, 2);
|
|
ilen = 2;
|
|
|
|
/* Check the sequence number. */
|
|
seq = (hdr[0] << 8) + hdr[1];
|
|
LogPrintf(LogDEBUG, "DeflateInput: Seq %d\n", seq);
|
|
if (seq != InputState.seqno) {
|
|
LogPrintf(LogERROR, "DeflateInput: Seq error: Got %d, expected %d\n",
|
|
seq, InputState.seqno);
|
|
pfree(mi_head);
|
|
CcpSendResetReq(&CcpFsm);
|
|
return NULL;
|
|
}
|
|
InputState.seqno++;
|
|
|
|
/* Allocate an output mbuf */
|
|
mo_head = mo = mballoc(DEFLATE_CHUNK_LEN, MB_IPIN);
|
|
|
|
/* Our proto starts with 0 if it's compressed */
|
|
wp = MBUF_CTOP(mo);
|
|
wp[0] = '\0';
|
|
|
|
/*
|
|
* We set avail_out to 1 initially so we can look at the first
|
|
* byte of the output and decide whether we have a compressed
|
|
* proto field.
|
|
*/
|
|
InputState.cx.next_in = MBUF_CTOP(mi);
|
|
InputState.cx.avail_in = mi->cnt;
|
|
InputState.cx.next_out = wp + 1;
|
|
InputState.cx.avail_out = 1;
|
|
ilen += mi->cnt;
|
|
|
|
flush = mi->next ? Z_NO_FLUSH : Z_SYNC_FLUSH;
|
|
first = 1;
|
|
olen = 0;
|
|
|
|
while (1) {
|
|
if ((res = inflate(&InputState.cx, flush)) != Z_OK) {
|
|
if (res == Z_STREAM_END)
|
|
break; /* Done */
|
|
LogPrintf(LogERROR, "DeflateInput: inflate returned %d (%s)\n",
|
|
res, InputState.cx.msg ? InputState.cx.msg : "");
|
|
pfree(mo_head);
|
|
pfree(mi);
|
|
CcpSendResetReq(&CcpFsm);
|
|
return NULL;
|
|
}
|
|
|
|
if (flush == Z_SYNC_FLUSH && InputState.cx.avail_out != 0)
|
|
break;
|
|
|
|
if (InputState.cx.avail_in == 0 && mi && (mi = mbfree(mi)) != NULL) {
|
|
/* underflow */
|
|
InputState.cx.next_in = MBUF_CTOP(mi);
|
|
ilen += (InputState.cx.avail_in = mi->cnt);
|
|
if (mi->next == NULL)
|
|
flush = Z_SYNC_FLUSH;
|
|
}
|
|
|
|
if (InputState.cx.avail_out == 0)
|
|
/* overflow */
|
|
if (first) {
|
|
if (!(wp[1] & 1)) {
|
|
/* 2 byte proto, shuffle it back in output */
|
|
wp[0] = wp[1];
|
|
InputState.cx.next_out--;
|
|
InputState.cx.avail_out = DEFLATE_CHUNK_LEN-1;
|
|
} else
|
|
InputState.cx.avail_out = DEFLATE_CHUNK_LEN-2;
|
|
first = 0;
|
|
} else {
|
|
olen += (mo->cnt = DEFLATE_CHUNK_LEN);
|
|
mo->next = mballoc(DEFLATE_CHUNK_LEN, MB_IPIN);
|
|
mo = mo->next;
|
|
InputState.cx.next_out = MBUF_CTOP(mo);
|
|
InputState.cx.avail_out = DEFLATE_CHUNK_LEN;
|
|
}
|
|
}
|
|
|
|
if (mi != NULL)
|
|
pfree(mi);
|
|
|
|
if (first) {
|
|
LogPrintf(LogERROR, "DeflateInput: Length error\n");
|
|
pfree(mo_head);
|
|
CcpSendResetReq(&CcpFsm);
|
|
return NULL;
|
|
}
|
|
|
|
olen += (mo->cnt = DEFLATE_CHUNK_LEN - InputState.cx.avail_out);
|
|
|
|
*proto = ((u_short)wp[0] << 8) | wp[1];
|
|
mo_head->offset += 2;
|
|
mo_head->cnt -= 2;
|
|
olen -= 2;
|
|
|
|
CcpInfo.compin += ilen;
|
|
CcpInfo.uncompin += olen;
|
|
|
|
LogPrintf(LogDEBUG, "DeflateInput: %d => %d bytes, proto 0x%04x\n",
|
|
ilen, olen, *proto);
|
|
|
|
/*
|
|
* Simulate an EMPTY_BLOCK so that our dictionary stays in sync.
|
|
* The peer will have silently removed this!
|
|
*/
|
|
InputState.cx.next_out = garbage;
|
|
InputState.cx.avail_out = sizeof garbage;
|
|
InputState.cx.next_in = EMPTY_BLOCK;
|
|
InputState.cx.avail_in = sizeof EMPTY_BLOCK;
|
|
inflate(&InputState.cx, Z_SYNC_FLUSH);
|
|
|
|
return mo_head;
|
|
}
|
|
|
|
static void
|
|
DeflateDictSetup(u_short proto, struct mbuf *mi)
|
|
{
|
|
int res, flush, expect_error;
|
|
u_char *rp;
|
|
struct mbuf *mi_head;
|
|
short len;
|
|
|
|
LogPrintf(LogDEBUG, "DeflateDictSetup: Got seq %d\n", InputState.seqno);
|
|
|
|
/*
|
|
* Stuff an ``uncompressed data'' block header followed by the
|
|
* protocol in front of the input
|
|
*/
|
|
mi_head = mballoc(7, MB_HDLCOUT);
|
|
mi_head->next = mi;
|
|
len = plength(mi);
|
|
mi = mi_head;
|
|
rp = MBUF_CTOP(mi);
|
|
if (proto < 0x100) { /* Compress the protocol */
|
|
rp[5] = proto & 0377;
|
|
mi->cnt = 6;
|
|
len++;
|
|
} else { /* Don't compress the protocol */
|
|
rp[5] = proto >> 8;
|
|
rp[6] = proto & 0377;
|
|
mi->cnt = 7;
|
|
len += 2;
|
|
}
|
|
rp[0] = 0x80; /* BITS: 100xxxxx */
|
|
rp[1] = len & 0377; /* The length */
|
|
rp[2] = len >> 8;
|
|
rp[3] = (~len) & 0377; /* One's compliment of the length */
|
|
rp[4] = (~len) >> 8;
|
|
|
|
InputState.cx.next_in = rp;
|
|
InputState.cx.avail_in = mi->cnt;
|
|
InputState.cx.next_out = garbage;
|
|
InputState.cx.avail_out = sizeof garbage;
|
|
flush = Z_NO_FLUSH;
|
|
expect_error = 0;
|
|
|
|
while (1) {
|
|
if ((res = inflate(&InputState.cx, flush)) != Z_OK) {
|
|
if (res == Z_STREAM_END)
|
|
break; /* Done */
|
|
if (expect_error && res == Z_BUF_ERROR)
|
|
break;
|
|
LogPrintf(LogERROR, "DeflateDictSetup: inflate returned %d (%s)\n",
|
|
res, InputState.cx.msg ? InputState.cx.msg : "");
|
|
LogPrintf(LogERROR, "DeflateDictSetup: avail_in %d, avail_out %d\n",
|
|
InputState.cx.avail_in, InputState.cx.avail_out);
|
|
CcpSendResetReq(&CcpFsm);
|
|
mbfree(mi_head); /* lose our allocated ``head'' buf */
|
|
return;
|
|
}
|
|
|
|
if (flush == Z_SYNC_FLUSH && InputState.cx.avail_out != 0)
|
|
break;
|
|
|
|
if (InputState.cx.avail_in == 0 && mi && (mi = mi->next) != NULL) {
|
|
/* underflow */
|
|
InputState.cx.next_in = MBUF_CTOP(mi);
|
|
InputState.cx.avail_in = mi->cnt;
|
|
if (mi->next == NULL)
|
|
flush = Z_SYNC_FLUSH;
|
|
}
|
|
|
|
if (InputState.cx.avail_out == 0) {
|
|
if (InputState.cx.avail_in == 0)
|
|
/*
|
|
* This seems to be a bug in libz ! If inflate() finished
|
|
* with 0 avail_in and 0 avail_out *and* this is the end of
|
|
* our input *and* inflate() *has* actually written all the
|
|
* output it's going to, it *doesn't* return Z_STREAM_END !
|
|
* When we subsequently call it with no more input, it gives
|
|
* us Z_BUF_ERROR :-( It seems pretty safe to ignore this
|
|
* error (the dictionary seems to stay in sync). In the worst
|
|
* case, we'll drop the next compressed packet and do a
|
|
* CcpReset() then.
|
|
*/
|
|
expect_error = 1;
|
|
/* overflow */
|
|
InputState.cx.next_out = garbage;
|
|
InputState.cx.avail_out = sizeof garbage;
|
|
}
|
|
}
|
|
|
|
CcpInfo.compin += len;
|
|
CcpInfo.uncompin += len;
|
|
|
|
InputState.seqno++;
|
|
mbfree(mi_head); /* lose our allocated ``head'' buf */
|
|
}
|
|
|
|
static const char *
|
|
DeflateDispOpts(struct lcp_opt *o)
|
|
{
|
|
static char disp[7];
|
|
|
|
sprintf(disp, "win %d", (o->data[0]>>4) + 8);
|
|
return disp;
|
|
}
|
|
|
|
static void
|
|
DeflateGetInputOpts(struct lcp_opt *o)
|
|
{
|
|
o->id = TY_DEFLATE;
|
|
o->len = 4;
|
|
o->data[0] = ((iWindowSize-8)<<4)+8;
|
|
o->data[1] = '\0';
|
|
}
|
|
|
|
static void
|
|
DeflateGetOutputOpts(struct lcp_opt *o)
|
|
{
|
|
o->id = TY_DEFLATE;
|
|
o->len = 4;
|
|
o->data[0] = ((oWindowSize-8)<<4)+8;
|
|
o->data[1] = '\0';
|
|
}
|
|
|
|
static void
|
|
PppdDeflateGetInputOpts(struct lcp_opt *o)
|
|
{
|
|
o->id = TY_PPPD_DEFLATE;
|
|
o->len = 4;
|
|
o->data[0] = ((iWindowSize-8)<<4)+8;
|
|
o->data[1] = '\0';
|
|
}
|
|
|
|
static void
|
|
PppdDeflateGetOutputOpts(struct lcp_opt *o)
|
|
{
|
|
o->id = TY_PPPD_DEFLATE;
|
|
o->len = 4;
|
|
o->data[0] = ((oWindowSize-8)<<4)+8;
|
|
o->data[1] = '\0';
|
|
}
|
|
|
|
static int
|
|
DeflateSetOpts(struct lcp_opt *o, int *sz)
|
|
{
|
|
if (o->len != 4 || (o->data[0]&15) != 8 || o->data[1] != '\0') {
|
|
return MODE_REJ;
|
|
}
|
|
*sz = (o->data[0] >> 4) + 8;
|
|
if (*sz > 15) {
|
|
*sz = 15;
|
|
return MODE_NAK;
|
|
}
|
|
|
|
return MODE_ACK;
|
|
}
|
|
|
|
static int
|
|
DeflateSetInputOpts(struct lcp_opt *o)
|
|
{
|
|
int res;
|
|
res = DeflateSetOpts(o, &iWindowSize);
|
|
if (res != MODE_ACK)
|
|
DeflateGetInputOpts(o);
|
|
return res;
|
|
}
|
|
|
|
static int
|
|
DeflateSetOutputOpts(struct lcp_opt *o)
|
|
{
|
|
int res;
|
|
res = DeflateSetOpts(o, &oWindowSize);
|
|
if (res != MODE_ACK)
|
|
DeflateGetOutputOpts(o);
|
|
return res;
|
|
}
|
|
|
|
static int
|
|
PppdDeflateSetInputOpts(struct lcp_opt *o)
|
|
{
|
|
int res;
|
|
res = DeflateSetOpts(o, &iWindowSize);
|
|
if (res != MODE_ACK)
|
|
PppdDeflateGetInputOpts(o);
|
|
return res;
|
|
}
|
|
|
|
static int
|
|
PppdDeflateSetOutputOpts(struct lcp_opt *o)
|
|
{
|
|
int res;
|
|
res = DeflateSetOpts(o, &oWindowSize);
|
|
if (res != MODE_ACK)
|
|
PppdDeflateGetOutputOpts(o);
|
|
return res;
|
|
}
|
|
|
|
static int
|
|
DeflateInitInput(void)
|
|
{
|
|
InputState.cx.zalloc = NULL;
|
|
InputState.cx.opaque = NULL;
|
|
InputState.cx.zfree = NULL;
|
|
InputState.cx.next_out = NULL;
|
|
if (inflateInit2(&InputState.cx, -iWindowSize) != Z_OK)
|
|
return 0;
|
|
DeflateResetInput();
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
DeflateInitOutput(void)
|
|
{
|
|
OutputState.cx.zalloc = NULL;
|
|
OutputState.cx.opaque = NULL;
|
|
OutputState.cx.zfree = NULL;
|
|
OutputState.cx.next_in = NULL;
|
|
if (deflateInit2(&OutputState.cx, Z_DEFAULT_COMPRESSION, 8,
|
|
-oWindowSize, 8, Z_DEFAULT_STRATEGY) != Z_OK)
|
|
return 0;
|
|
DeflateResetOutput();
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
DeflateTermInput(void)
|
|
{
|
|
iWindowSize = 15;
|
|
inflateEnd(&InputState.cx);
|
|
}
|
|
|
|
static void
|
|
DeflateTermOutput(void)
|
|
{
|
|
oWindowSize = 15;
|
|
deflateEnd(&OutputState.cx);
|
|
}
|
|
|
|
const struct ccp_algorithm PppdDeflateAlgorithm = {
|
|
TY_PPPD_DEFLATE, /* pppd (wrongly) expects this ``type'' field */
|
|
ConfPppdDeflate,
|
|
DeflateDispOpts,
|
|
{
|
|
PppdDeflateGetInputOpts,
|
|
PppdDeflateSetInputOpts,
|
|
DeflateInitInput,
|
|
DeflateTermInput,
|
|
DeflateResetInput,
|
|
DeflateInput,
|
|
DeflateDictSetup
|
|
},
|
|
{
|
|
PppdDeflateGetOutputOpts,
|
|
PppdDeflateSetOutputOpts,
|
|
DeflateInitOutput,
|
|
DeflateTermOutput,
|
|
DeflateResetOutput,
|
|
DeflateOutput
|
|
},
|
|
};
|
|
|
|
const struct ccp_algorithm DeflateAlgorithm = {
|
|
TY_DEFLATE, /* rfc 1979 */
|
|
ConfDeflate,
|
|
DeflateDispOpts,
|
|
{
|
|
DeflateGetInputOpts,
|
|
DeflateSetInputOpts,
|
|
DeflateInitInput,
|
|
DeflateTermInput,
|
|
DeflateResetInput,
|
|
DeflateInput,
|
|
DeflateDictSetup
|
|
},
|
|
{
|
|
DeflateGetOutputOpts,
|
|
DeflateSetOutputOpts,
|
|
DeflateInitOutput,
|
|
DeflateTermOutput,
|
|
DeflateResetOutput,
|
|
DeflateOutput
|
|
},
|
|
};
|