mods for broken compilers which can't nest ntohl etc.
[ppp.git] / modules / bsd-comp.c
index b730c0738953b89040ef4c2b5ed35d1d46c90d32..48bde3125d84dbdd6ace6819b4afd530b9e0e310 100644 (file)
  */
 
 /*
- * This version is for use with STREAMS under SunOS 4.x.
+ * This version is for use with STREAMS under SunOS 4.x,
+ * DEC Alpha OSF/1, and AIX 4.x.
  *
- * $Id: bsd-comp.c,v 1.3 1994/08/31 23:58:53 paulus Exp $
+ * $Id: bsd-comp.c,v 1.12 1995/05/19 03:48:34 paulus Exp $
  */
 
+#ifdef __aix4__
+#include <net/net_globals.h>
+#endif
 #include <sys/param.h>
 #include <sys/types.h>
 #include <sys/stream.h>
-#include <sys/kmem_alloc.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <net/ppp_defs.h>
 #include <net/ppp_str.h>
 
-#define PACKET mblk_t
+#ifdef sun
+#ifdef __svr4__                        /* SunOS 5.x */
+#include <sys/kmem.h>
+#define ALLOCATE(n)    kmem_alloc((n), KM_NOSLEEP)
+#else                          /* SunOS 4.x */
+#include <sys/kmem_alloc.h>
+#define ALLOCATE(n)    kmem_alloc((n), KMEM_NOSLEEP)
+#endif
+#define FREE(p, n)     kmem_free((p), (n))
+#endif
+
+#ifdef __osf__
+#include <kern/kalloc.h>
+#define ALLOCATE(n)    kalloc((n))
+#define FREE(p, n)     kfree((p), (n))
+#endif
+
+#ifdef __aix4__
+#define ALLOCATE(n)    xmalloc((n), 0, pinned_heap)
+#define FREE(p, n)     xmfree((p), pinned_heap)
+#endif
+
+#define PACKETPTR      mblk_t *
 #include <net/ppp-comp.h>
 
+#if DO_BSD_COMPRESS
+
 /*
  * PPP "BSD compress" compression
  *  The differences between this compression and the classic BSD LZW
@@ -82,22 +112,29 @@ struct bsd_db {
     u_int   hsize;                     /* size of the hash table */
     u_char  hshift;                    /* used in hash function */
     u_char  n_bits;                    /* current bits/code */
-    char    debug;
+    u_char  maxbits;
+    u_char  debug;
     u_char  unit;
-    u_short mru;
+    u_short seqno;                     /* sequence number of next packet */
+    u_int   hdrlen;                    /* header length to preallocate */
+    u_int   mru;
     u_int   maxmaxcode;                        /* largest valid code */
     u_int   max_ent;                   /* largest code in use */
-    u_long  seqno;                     /* # of last byte of packet */
-    u_long  in_count;                  /* uncompressed bytes */
-    u_long  bytes_out;                 /* compressed bytes */
-    u_long  ratio;                     /* recent compression ratio */
-    u_long  checkpoint;                        /* when to next check the ratio */
-    int            clear_count;                /* times dictionary cleared */
-    int            incomp_count;               /* incompressible packets */
+    u_int   in_count;                  /* uncompressed bytes, aged */
+    u_int   bytes_out;                 /* compressed bytes, aged */
+    u_int   ratio;                     /* recent compression ratio */
+    u_int   checkpoint;                        /* when to next check the ratio */
+    u_int   clear_count;               /* times dictionary cleared */
+    u_int   incomp_count;              /* incompressible packets */
+    u_int   incomp_bytes;              /* incompressible bytes */
+    u_int   uncomp_count;              /* uncompressed packets */
+    u_int   uncomp_bytes;              /* uncompressed bytes */
+    u_int   comp_count;                        /* compressed packets */
+    u_int   comp_bytes;                        /* compressed bytes */
     u_short *lens;                     /* array of lengths of codes */
     struct bsd_dict {
        union {                         /* hash value */
-           u_long      fcode;
+           u_int32_t   fcode;
            struct {
 #ifdef BSD_LITTLE_ENDIAN
                u_short prefix;         /* preceding code */
@@ -115,40 +152,41 @@ struct bsd_db {
     } dict[1];
 };
 
-#define BSD_OVHD       3               /* BSD compress overhead/packet */
-#define MIN_BSD_BITS   9
-#define BSD_INIT_BITS  MIN_BSD_BITS
-#define MAX_BSD_BITS   15
+#define BSD_OVHD       2               /* BSD compress overhead/packet */
+#define BSD_INIT_BITS  BSD_MIN_BITS
 
 static void    *bsd_comp_alloc __P((u_char *options, int opt_len));
 static void    *bsd_decomp_alloc __P((u_char *options, int opt_len));
 static void    bsd_free __P((void *state));
 static int     bsd_comp_init __P((void *state, u_char *options, int opt_len,
-                                  int unit, int debug));
+                                  int unit, int hdrlen, int debug));
 static int     bsd_decomp_init __P((void *state, u_char *options, int opt_len,
-                                    int unit, int mru, int debug));
+                                    int unit, int hdrlen, int mru, int debug));
 static int     bsd_compress __P((void *state, mblk_t **mret,
                                  mblk_t *mp, int slen, int maxolen));
 static void    bsd_incomp __P((void *state, mblk_t *dmsg));
-static mblk_t  *bsd_decompress __P((void *state, mblk_t *cmp, int hdroff));
+static int     bsd_decompress __P((void *state, mblk_t *cmp, mblk_t **dmpp));
 static void    bsd_reset __P((void *state));
+static void    bsd_comp_stats __P((void *state, struct compstat *stats));
 
 /*
  * Procedures exported to ppp_comp.c.
  */
 struct compressor ppp_bsd_compress = {
-    0x21,                      /* compress_proto */
+    CI_BSD_COMPRESS,           /* compress_proto */
     bsd_comp_alloc,            /* comp_alloc */
     bsd_free,                  /* comp_free */
     bsd_comp_init,             /* comp_init */
     bsd_reset,                 /* comp_reset */
     bsd_compress,              /* compress */
+    bsd_comp_stats,            /* comp_stat */
     bsd_decomp_alloc,          /* decomp_alloc */
     bsd_free,                  /* decomp_free */
     bsd_decomp_init,           /* decomp_init */
     bsd_reset,                 /* decomp_reset */
     bsd_decompress,            /* decompress */
     bsd_incomp,                        /* incomp */
+    bsd_comp_stats,            /* decomp_stat */
 };
 
 /*
@@ -160,14 +198,20 @@ struct compressor ppp_bsd_compress = {
 #define LAST   255
 
 #define MAXCODE(b)     ((1 << (b)) - 1)
-#define BADCODEM1      MAXCODE(MAX_BSD_BITS);
+#define BADCODEM1      MAXCODE(BSD_MAX_BITS)
 
-#define BSD_HASH(prefix,suffix,hshift) ((((u_long)(suffix)) << (hshift)) \
-                                      ^ (u_long)(prefix))
-#define BSD_KEY(prefix,suffix) ((((u_long)(suffix)) << 16) + (u_long)(prefix))
+#define BSD_HASH(prefix,suffix,hshift) ((((u_int32_t)(suffix)) << (hshift)) \
+                                        ^ (u_int32_t)(prefix))
+#define BSD_KEY(prefix,suffix)         ((((u_int32_t)(suffix)) << 16) \
+                                        + (u_int32_t)(prefix))
 
 #define CHECK_GAP      10000           /* Ratio check interval */
 
+#define RATIO_SCALE_LOG        8
+#define RATIO_SCALE    (1<<RATIO_SCALE_LOG)
+#define RATIO_MAX      (0x7fffffff>>RATIO_SCALE_LOG)
+
+#define DECOMP_CHUNK   256
 
 /*
  * clear the dictionary
@@ -203,12 +247,12 @@ static int                                /* 1=output CLEAR */
 bsd_check(db)
     struct bsd_db *db;
 {
-    u_long new_ratio;
+    u_int new_ratio;
 
     if (db->in_count >= db->checkpoint) {
        /* age the ratio by limiting the size of the counts */
-       if (db->in_count >= 0x7fffff
-           || db->bytes_out >= 0x7fffff) {
+       if (db->in_count >= RATIO_MAX
+           || db->bytes_out >= RATIO_MAX) {
            db->in_count -= db->in_count/4;
            db->bytes_out -= db->bytes_out/4;
        }
@@ -221,13 +265,13 @@ bsd_check(db)
             * by incompressible data.
             *
             * This does not overflow, because
-            *  db->in_count <= 0x7fffff.
+            *  db->in_count <= RATIO_MAX.
             */
-           new_ratio = db->in_count<<8;
+           new_ratio = db->in_count << RATIO_SCALE_LOG;
            if (db->bytes_out != 0)
                new_ratio /= db->bytes_out;
 
-           if (new_ratio < db->ratio || new_ratio < 256) {
+           if (new_ratio < db->ratio || new_ratio < 1 * RATIO_SCALE) {
                bsd_clear(db);
                return 1;
            }
@@ -237,6 +281,33 @@ bsd_check(db)
     return 0;
 }
 
+/*
+ * Return statistics.
+ */
+static void
+bsd_comp_stats(state, stats)
+    void *state;
+    struct compstat *stats;
+{
+    struct bsd_db *db = (struct bsd_db *) state;
+    u_int out;
+
+    stats->unc_bytes = db->uncomp_bytes;
+    stats->unc_packets = db->uncomp_count;
+    stats->comp_bytes = db->comp_bytes;
+    stats->comp_packets = db->comp_count;
+    stats->inc_bytes = db->incomp_bytes;
+    stats->inc_packets = db->incomp_count;
+    stats->ratio = db->in_count;
+    out = db->bytes_out;
+    if (stats->ratio <= 0x7fffff)
+       stats->ratio <<= 8;
+    else
+       out >>= 8;
+    if (out != 0)
+       stats->ratio /= out;
+}
+
 /*
  * Reset state, as on a CCP ResetReq.
  */
@@ -263,9 +334,10 @@ bsd_alloc(options, opt_len, decomp)
     u_int newlen, hsize, hshift, maxmaxcode;
     struct bsd_db *db;
 
-    if (opt_len != 3 || options[0] != 0x21 || options[1] != 3)
+    if (opt_len != 3 || options[0] != CI_BSD_COMPRESS || options[1] != 3
+       || BSD_VERSION(options[2]) != BSD_CURRENT_VERSION)
        return NULL;
-    bits = options[2];
+    bits = BSD_NBITS(options[2]);
     switch (bits) {
     case 9:                    /* needs 82152 for both directions */
     case 10:                   /* needs 84144 */
@@ -296,7 +368,7 @@ bsd_alloc(options, opt_len, decomp)
 
     maxmaxcode = MAXCODE(bits);
     newlen = sizeof(*db) + (hsize-1) * (sizeof(db->dict[0]));
-    db = (struct bsd_db *) kmem_alloc(newlen, KMEM_NOSLEEP);
+    db = (struct bsd_db *) ALLOCATE(newlen);
     if (!db)
        return NULL;
     bzero(db, sizeof(*db) - sizeof(db->dict));
@@ -304,10 +376,9 @@ bsd_alloc(options, opt_len, decomp)
     if (!decomp) {
        db->lens = NULL;
     } else {
-       db->lens = (u_short *) kmem_alloc((maxmaxcode+1) * sizeof(db->lens[0]),
-                                         KMEM_NOSLEEP);
+       db->lens = (u_short *) ALLOCATE((maxmaxcode+1) * sizeof(db->lens[0]));
        if (!db->lens) {
-           kmem_free(db, newlen);
+           FREE(db, newlen);
            return NULL;
        }
     }
@@ -316,6 +387,7 @@ bsd_alloc(options, opt_len, decomp)
     db->hsize = hsize;
     db->hshift = hshift;
     db->maxmaxcode = maxmaxcode;
+    db->maxbits = bits;
 
     return (void *) db;
 }
@@ -327,8 +399,8 @@ bsd_free(state)
     struct bsd_db *db = (struct bsd_db *) state;
 
     if (db->lens)
-       kmem_free(db->lens, (db->maxmaxcode+1) * sizeof(db->lens[0]));
-    kmem_free(db, db->totlen);
+       FREE(db->lens, (db->maxmaxcode+1) * sizeof(db->lens[0]));
+    FREE(db, db->totlen);
 }
 
 static void *
@@ -351,15 +423,16 @@ bsd_decomp_alloc(options, opt_len)
  * Initialize the database.
  */
 static int
-bsd_init(db, options, opt_len, unit, mru, debug, decomp)
+bsd_init(db, options, opt_len, unit, hdrlen, mru, debug, decomp)
     struct bsd_db *db;
     u_char *options;
-    int opt_len, unit, mru, debug, decomp;
+    int opt_len, unit, hdrlen, mru, debug, decomp;
 {
     int i;
 
-    if (opt_len != 3 || options[0] != 0x21 || options[1] != 3
-       || MAXCODE(options[2]) != db->maxmaxcode
+    if (opt_len != 3 || options[0] != CI_BSD_COMPRESS || options[1] != 3
+       || BSD_VERSION(options[2]) != BSD_CURRENT_VERSION
+       || BSD_NBITS(options[2]) != db->maxbits
        || decomp && db->lens == NULL)
        return 0;
 
@@ -375,48 +448,48 @@ bsd_init(db, options, opt_len, unit, mru, debug, decomp)
     }
 
     db->unit = unit;
+    db->hdrlen = hdrlen;
     db->mru = mru;
-    db->clear_count = -1;
     if (debug)
        db->debug = 1;
 
-    bsd_clear(db);
+    bsd_reset(db);
 
     return 1;
 }
 
 static int
-bsd_comp_init(state, options, opt_len, unit, debug)
+bsd_comp_init(state, options, opt_len, unit, hdrlen, debug)
     void *state;
     u_char *options;
-    int opt_len, unit, debug;
+    int opt_len, unit, hdrlen, debug;
 {
     return bsd_init((struct bsd_db *) state, options, opt_len,
-                   unit, 0, debug, 0);
+                   unit, hdrlen, 0, debug, 0);
 }
 
 static int
-bsd_decomp_init(state, options, opt_len, unit, mru, debug)
+bsd_decomp_init(state, options, opt_len, unit, hdrlen, mru, debug)
     void *state;
     u_char *options;
-    int opt_len, unit, mru, debug;
+    int opt_len, unit, hdrlen, mru, debug;
 {
     return bsd_init((struct bsd_db *) state, options, opt_len,
-                   unit, mru, debug, 1);
+                   unit, hdrlen, mru, debug, 1);
 }
 
 
-
 /*
  * compress a packet
- *     Assume the protocol is known to be >= 0x21 and < 0xff.
  *     One change from the BSD compress command is that when the
  *     code size expands, we do not output a bunch of padding.
+ *
+ * N.B. at present, we ignore the hdrlen specified in the comp_init call.
  */
 static int                     /* new slen */
-bsd_compress(state, mret, mp, slen, maxolen)
+bsd_compress(state, mretp, mp, slen, maxolen)
     void *state;
-    mblk_t **mret            /* return compressed mbuf chain here */
+    mblk_t **mretp;            /* return compressed mbuf chain here */
     mblk_t *mp;                        /* from here */
     int slen;                  /* uncompressed length */
     int maxolen;               /* max compressed length */
@@ -426,17 +499,15 @@ bsd_compress(state, mret, mp, slen, maxolen)
     u_int max_ent = db->max_ent;
     u_int n_bits = db->n_bits;
     u_int bitno = 32;
-    u_long accm = 0;
+    u_int32_t accm = 0, fcode;
     struct bsd_dict *dictp;
-    u_long fcode;
     u_char c;
-    long hval, disp, ent;
-    mblk_t *np;
+    int hval, disp, ent, ilen;
+    mblk_t *np, *mret;
     u_char *rptr, *wptr;
     u_char *cp_end;
     int olen;
     mblk_t *m, **mnp;
-    int proto;
 
 #define PUTBYTE(v) {                                   \
     if (wptr) {                                                \
@@ -464,20 +535,35 @@ bsd_compress(state, mret, mp, slen, maxolen)
     } while (bitno <= 24);                             \
 }
 
+    /*
+     * First get the protocol and check that we're
+     * interested in this packet.
+     */
+    *mretp = NULL;
+    rptr = mp->b_rptr;
+    if (rptr + PPP_HDRLEN > mp->b_wptr) {
+       if (!pullupmsg(mp, PPP_HDRLEN))
+           return 0;
+       rptr = mp->b_rptr;
+    }
+    ent = PPP_PROTOCOL(rptr);          /* get the protocol */
+    if (ent < 0x21 || ent > 0xf9)
+       return 0;
+
     /* Don't generate compressed packets which are larger than
        the uncompressed packet. */
     if (maxolen > slen)
        maxolen = slen;
 
     /* Allocate enough message blocks to give maxolen total space. */
-    mnp = mret;
+    mnp = &mret;
     for (olen = maxolen; olen > 0; ) {
        m = allocb((olen < 4096? olen: 4096), BPRI_MED);
        *mnp = m;
        if (m == NULL) {
-           if (*mret != NULL) {
-               freemsg(*mret);
-               mnp = mret;
+           if (mret != NULL) {
+               freemsg(mret);
+               mnp = &mret;
            }
            break;
        }
@@ -486,8 +572,7 @@ bsd_compress(state, mret, mp, slen, maxolen)
     }
     *mnp = NULL;
 
-    rptr = mp->b_rptr;
-    if ((m = *mret) != NULL) {
+    if ((m = mret) != NULL) {
        wptr = m->b_wptr;
        cp_end = m->b_datap->db_lim;
     } else
@@ -496,26 +581,22 @@ bsd_compress(state, mret, mp, slen, maxolen)
 
     /*
      * Copy the PPP header over, changing the protocol,
-     * and install the 3-byte sequence number.
+     * and install the 2-byte sequence number.
      */
-    slen += db->seqno - PPP_HDRLEN + 1;
-    db->seqno = slen;
     if (wptr) {
-       wptr[0] = rptr[0];      /* assumes the ppp header is */
-       wptr[1] = rptr[1];      /* all in one mblk */
+       wptr[0] = PPP_ADDRESS(rptr);
+       wptr[1] = PPP_CONTROL(rptr);
        wptr[2] = 0;            /* change the protocol */
        wptr[3] = PPP_COMP;
-       wptr[4] = slen>>16;
-       wptr[5] = slen>>8;
-       wptr[6] = slen;
+       wptr[4] = db->seqno >> 8;
+       wptr[5] = db->seqno;
        wptr += PPP_HDRLEN + BSD_OVHD;
     }
-
-    /* start with the protocol byte */
-    ent = rptr[3];
+    ++db->seqno;
     rptr += PPP_HDRLEN;
+
     slen = mp->b_wptr - rptr;
-    db->in_count += slen + 1;
+    ilen = slen + 1;
     np = mp->b_cont;
     for (;;) {
        if (slen <= 0) {
@@ -526,7 +607,7 @@ bsd_compress(state, mret, mp, slen, maxolen)
            np = np->b_cont;
            if (!slen)
                continue;   /* handle 0-length buffers */
-           db->in_count += slen;
+           ilen += slen;
        }
 
        slen--;
@@ -553,7 +634,7 @@ bsd_compress(state, mret, mp, slen, maxolen)
            if (dictp->codem1 >= max_ent)
                goto nomatch;
        } while (dictp->f.fcode != fcode);
-       ent = dictp->codem1+1;          /* finally found (prefix,suffix) */
+       ent = dictp->codem1 + 1;        /* finally found (prefix,suffix) */
        continue;
 
     nomatch:
@@ -581,36 +662,48 @@ bsd_compress(state, mret, mp, slen, maxolen)
        ent = c;
     }
 
-    OUTPUT(ent);                       /* output the last code */
+    OUTPUT(ent);               /* output the last code */
     db->bytes_out += olen;
+    db->in_count += ilen;
+    if (bitno < 32)
+       ++db->bytes_out;        /* count complete bytes */
 
     if (bsd_check(db))
-       OUTPUT(CLEAR);                  /* do not count the CLEAR */
+       OUTPUT(CLEAR);          /* do not count the CLEAR */
 
-    /* Pad dribble bits of last code with ones.
+    /*
+     * Pad dribble bits of last code with ones.
      * Do not emit a completely useless byte of ones.
      */
     if (bitno != 32)
        PUTBYTE((accm | (0xff << (bitno-8))) >> 24);
 
-    /* Increase code size if we would have without the packet
+    /*
+     * Increase code size if we would have without the packet
      * boundary and as the decompressor will.
      */
     if (max_ent >= MAXCODE(n_bits) && max_ent < db->maxmaxcode)
        db->n_bits++;
 
-    if (olen + PPP_HDRLEN + BSD_OVHD > maxolen && *mret != NULL) {
+    db->uncomp_bytes += ilen;
+    ++db->uncomp_count;
+    if (olen + PPP_HDRLEN + BSD_OVHD > maxolen && mret != NULL) {
        /* throw away the compressed stuff if it is longer than uncompressed */
-       freemsg(*mret);
-       *mret = NULL;
+       freemsg(mret);
+       mret = NULL;
+       ++db->incomp_count;
+       db->incomp_bytes += ilen;
     } else if (wptr != NULL) {
        m->b_wptr = wptr;
        if (m->b_cont) {
            freemsg(m->b_cont);
            m->b_cont = NULL;
        }
+       ++db->comp_count;
+       db->comp_bytes += olen + BSD_OVHD;
     }
 
+    *mretp = mret;
     return olen + PPP_HDRLEN + BSD_OVHD;
 #undef OUTPUT
 #undef PUTBYTE
@@ -620,7 +713,6 @@ bsd_compress(state, mret, mp, slen, maxolen)
 /*
  * Update the "BSD Compress" dictionary on the receiver for
  * incompressible data by pretending to compress the incoming data.
- * The protocol is assumed to be < 0x100.
  */
 static void
 bsd_incomp(state, dmsg)
@@ -632,20 +724,27 @@ bsd_incomp(state, dmsg)
     u_int max_ent = db->max_ent;
     u_int n_bits = db->n_bits;
     struct bsd_dict *dictp;
-    u_long fcode;
+    u_int32_t fcode;
     u_char c;
     long hval, disp;
-    int slen;
+    int slen, ilen;
     u_int bitno = 7;
     u_char *rptr;
     u_int ent;
 
-    db->incomp_count++;
+    rptr = dmsg->b_rptr;
+    if (rptr + PPP_HDRLEN > dmsg->b_wptr) {
+       if (!pullupmsg(dmsg, PPP_HDRLEN))
+           return;
+       rptr = dmsg->b_rptr;
+    }
+    ent = PPP_PROTOCOL(rptr);          /* get the protocol */
+    if (ent < 0x21 || ent > 0xf9)
+       return;
 
+    db->incomp_count++;
     db->seqno++;
-    db->in_count++;            /* count the protocol as 1 byte */
-    rptr = dmsg->b_rptr;
-    ent = rptr[3];             /* get the protocol */
+    ilen = 1;          /* count the protocol as 1 byte */
     rptr += PPP_HDRLEN;
     for (;;) {
        slen = dmsg->b_wptr - rptr;
@@ -656,8 +755,7 @@ bsd_incomp(state, dmsg)
            rptr = dmsg->b_rptr;
            continue;           /* skip zero-length buffers */
        }
-       db->in_count += slen;
-       db->seqno += slen;
+       ilen += slen;
 
        do {
            c = *rptr++;
@@ -714,8 +812,14 @@ bsd_incomp(state, dmsg)
     }
     bitno += n_bits;           /* output (count) the last code */
     db->bytes_out += bitno/8;
+    db->in_count += ilen;
     (void)bsd_check(db);
 
+    ++db->incomp_count;
+    db->incomp_bytes += ilen;
+    ++db->uncomp_count;
+    db->uncomp_bytes += ilen;
+
     /* Increase code size if we would have without the packet
      * boundary and as the decompressor will.
      */
@@ -726,16 +830,28 @@ bsd_incomp(state, dmsg)
 
 /*
  * Decompress "BSD Compress"
+ *
+ * Because of patent problems, we return DECOMP_ERROR for errors
+ * found by inspecting the input data and for system problems, but
+ * DECOMP_FATALERROR for any errors which could possibly be said to
+ * be being detected "after" decompression.  For DECOMP_ERROR,
+ * we can issue a CCP reset-request; for DECOMP_FATALERROR, we may be
+ * infringing a patent of Motorola's if we do, so we take CCP down
+ * instead.
+ *
+ * Given that the frame has the correct sequence number and a good FCS,
+ * errors such as invalid codes in the input most likely indicate a
+ * bug, so we return DECOMP_FATALERROR for them in order to turn off
+ * compression, even though they are detected by inspecting the input.
  */
-static mblk_t *                                /* 0=failed, so zap CCP */
-bsd_decompress(state, cmsg, hdroff)
+static int
+bsd_decompress(state, cmsg, dmpp)
     void *state;
-    mblk_t *cmsg;
-    int hdroff;
+    mblk_t *cmsg, **dmpp;
 {
     struct bsd_db *db = (struct bsd_db *) state;
     u_int max_ent = db->max_ent;
-    u_long accm = 0;
+    u_int32_t accm = 0;
     u_int bitno = 32;          /* 1st valid bit in accm */
     u_int n_bits = db->n_bits;
     u_int tgtbitno = 32-n_bits;        /* bitno when we have a code */
@@ -743,19 +859,19 @@ bsd_decompress(state, cmsg, hdroff)
     int explen, i, seq, len;
     u_int incode, oldcode, finchar;
     u_char *p, *rptr, *wptr;
-    mblk_t *dmsg;
-    int adrs, ctrl;
-    int dlen, space, codelen;
+    mblk_t *dmsg, *mret;
+    int adrs, ctrl, ilen;
+    int dlen, space, codelen, extra;
 
     /*
      * Get at least the BSD Compress header in the first buffer
      */
     rptr = cmsg->b_rptr;
-    if (rptr + PPP_HDRLEN + BSD_OVHD <= cmsg->b_wptr) {
+    if (rptr + PPP_HDRLEN + BSD_OVHD >= cmsg->b_wptr) {
        if (!pullupmsg(cmsg, PPP_HDRLEN + BSD_OVHD + 1)) {
            if (db->debug)
                printf("bsd_decomp%d: failed to pullup\n", db->unit);
-           return 0;
+           return DECOMP_ERROR;
        }
        rptr = cmsg->b_rptr;
     }
@@ -767,31 +883,28 @@ bsd_decompress(state, cmsg, hdroff)
     adrs = PPP_ADDRESS(rptr);
     ctrl = PPP_CONTROL(rptr);
     rptr += PPP_HDRLEN;
-    seq = (rptr[0] << 16) + (rptr[1] << 8) + rptr[2];
-    rptr += 3;
-    len = cmsg->b_wptr - rptr;
+    seq = (rptr[0] << 8) + rptr[1];
+    rptr += BSD_OVHD;
+    ilen = len = cmsg->b_wptr - rptr;
 
     /*
-     * Check the sequence number and give up if the length is nonsense.
-     * The check is against mru+1 because we compress one byte of protocol.
+     * Check the sequence number and give up if it is not what we expect.
      */
-    explen = (seq - db->seqno) & 0xffffff;
-    db->seqno = seq;
-    if (explen > db->mru + 1 || explen < 1) {
+    if (seq != db->seqno++) {
        if (db->debug)
-           printf("bsd_decomp%d: bad length 0x%x\n", db->unit, explen);
-       return 0;
+           printf("bsd_decomp%d: bad sequence # %d, expected %d\n",
+                  db->unit, seq, db->seqno - 1);
+       return DECOMP_ERROR;
     }
 
-    /* allocate enough message blocks for the decompressed message */
-    dlen = explen + PPP_HDRLEN - 1 + hdroff;
-    /* XXX assume decompressed packet fits in a single block */
-    dmsg = allocb(dlen, BPRI_HI);
-    if (!dmsg) {
-       /* give up if cannot get an uncompressed buffer */
-       return 0;
-    }
-    wptr = dmsg->b_wptr;
+    /*
+     * Allocate one message block to start with.
+     */
+    if ((dmsg = allocb(DECOMP_CHUNK + db->hdrlen, BPRI_MED)) == NULL)
+       return DECOMP_ERROR;
+    mret = dmsg;
+    dmsg->b_wptr += db->hdrlen;
+    dmsg->b_rptr = wptr = dmsg->b_wptr;
 
     /* Fill in the ppp header, but not the last byte of the protocol
        (that comes from the decompressed data). */
@@ -801,29 +914,21 @@ bsd_decompress(state, cmsg, hdroff)
     wptr += PPP_HDRLEN - 1;
     space = dmsg->b_datap->db_lim - wptr;
 
-    db->bytes_out += len;
-    dlen = explen;
     oldcode = CLEAR;
+    explen = 0;
     for (;;) {
        if (len == 0) {
            cmsg = cmsg->b_cont;
-           if (!cmsg) {        /* quit at end of message */
-               if (dlen != 0) {
-                   freemsg(dmsg);
-                   if (db->debug)
-                       printf("bsd_decomp%d: lost %d bytes\n",
-                              db->unit, explen);
-                   return 0;
-               }
+           if (!cmsg)          /* quit at end of message */
                break;
-           }
            rptr = cmsg->b_rptr;
            len = cmsg->b_wptr - rptr;
-           db->bytes_out += len;
+           ilen += len;
            continue;           /* handle 0-length buffers */
        }
 
-       /* Accumulate bytes until we have a complete code.
+       /*
+        * Accumulate bytes until we have a complete code.
         * Then get the next code, relying on the 32-bit,
         * unsigned accm to mask the result.
         */
@@ -837,7 +942,8 @@ bsd_decompress(state, cmsg, hdroff)
        bitno += n_bits;
 
        if (incode == CLEAR) {
-           /* The dictionary must only be cleared at
+           /*
+            * The dictionary must only be cleared at
             * the end of a packet.  But there could be an
             * empty message block at the end.
             */
@@ -848,52 +954,61 @@ bsd_decompress(state, cmsg, hdroff)
                    freemsg(dmsg);
                    if (db->debug)
                        printf("bsd_decomp%d: bad CLEAR\n", db->unit);
-                   return 0;
+                   return DECOMP_FATALERROR;
                }
            }
            bsd_clear(db);
-           explen = 0;
+           explen = ilen = 0;
            break;
        }
 
+       if (incode > max_ent + 2 || incode > db->maxmaxcode
+           || incode > max_ent && oldcode == CLEAR) {
+           freemsg(dmsg);
+           if (db->debug) {
+               printf("bsd_decomp%d: bad code 0x%x oldcode=0x%x ",
+                      db->unit, incode, oldcode);
+               printf("max_ent=0x%x dlen=%d seqno=%d\n",
+                      max_ent, dlen, db->seqno);
+           }
+           return DECOMP_FATALERROR;   /* probably a bug */
+       }
+
        /* Special case for KwKwK string. */
        if (incode > max_ent) {
-           if (incode > max_ent+2 || incode > db->maxmaxcode
-               || oldcode == CLEAR) {
-               freemsg(dmsg);
-               if (db->debug) {
-                   printf("bsd_decomp%d: bad code 0x%x oldcode=0x%x ",
-                          db->unit, incode, oldcode);
-                   printf("max_ent=0x%x dlen=%d seqno=%d\n",
-                          max_ent, dlen, db->seqno);
-               }
-               return 0;
-           }
            finchar = oldcode;
-           --dlen;
-       } else
+           extra = 1;
+       } else {
            finchar = incode;
+           extra = 0;
+       }
 
        codelen = db->lens[finchar];
-       dlen -= codelen;
-       if (dlen < 0) {
+       explen += codelen + extra;
+       if (explen > db->mru + 1) {
            freemsg(dmsg);
            if (db->debug)
-               printf("bsd_decomp%d: ran out of buffer\n", db->unit);
-           return 0;
+               printf("bsd_decomp%d: ran out of mru\n", db->unit);
+           return DECOMP_FATALERROR;
        }
 
-       /* decode code and install in decompressed buffer */
-       space -= codelen;
+       /*
+        * Decode this code and install it in the decompressed buffer.
+        */
+       space -= codelen + extra;
        if (space < 0) {
-#ifdef DEBUG
-           if (cmsg->b_cont)
-               len += msgdsize(cmsg->b_cont);
-           printf("bsd_decomp%d: overran output by %d with %d bytes left\n",
-                  db->unit, -space, len);
-#endif
-           freemsg(dmsg);
-           return 0;
+           /* Allocate another message block. */
+           dmsg->b_wptr = wptr;
+           dlen = codelen + extra;
+           if (dlen < DECOMP_CHUNK)
+               dlen = DECOMP_CHUNK;
+           if ((dmsg->b_cont = allocb(dlen, BPRI_MED)) == NULL) {
+               freemsg(dmsg);
+               return DECOMP_ERROR;
+           }
+           dmsg = dmsg->b_cont;
+           wptr = dmsg->b_wptr;
+           space = dmsg->b_datap->db_lim - wptr - codelen - extra;
        }
        p = (wptr += codelen);
        while (finchar > LAST) {
@@ -905,7 +1020,7 @@ bsd_decompress(state, cmsg, hdroff)
                printf("bsd_decomp%d: fell off end of chain ", db->unit);
                printf("0x%x at 0x%x by 0x%x, max_ent=0x%x\n",
                       incode, finchar, db->dict[finchar].cptr, max_ent);
-               return 0;
+               return DECOMP_FATALERROR;
            }
            if (dictp->codem1 != finchar-1) {
                freemsg(dmsg);
@@ -913,7 +1028,7 @@ bsd_decompress(state, cmsg, hdroff)
                       db->unit, incode, finchar);
                printf("oldcode=0x%x cptr=0x%x codem1=0x%x\n", oldcode,
                       db->dict[finchar].cptr, dictp->codem1);
-               return 0;
+               return DECOMP_FATALERROR;
            }
 #endif
            *--p = dictp->f.hs.suffix;
@@ -927,10 +1042,8 @@ bsd_decompress(state, cmsg, hdroff)
                   db->unit, codelen, incode, max_ent);
 #endif
 
-       if (incode > max_ent) {         /* the KwKwK case again */
+       if (extra)              /* the KwKwK case again */
            *wptr++ = finchar;
-           --space;
-       }
 
        /*
         * If not first code in a packet, and
@@ -941,8 +1054,8 @@ bsd_decompress(state, cmsg, hdroff)
         */
        if (oldcode != CLEAR && max_ent < db->maxmaxcode) {
            struct bsd_dict *dictp2;
-           u_long fcode;
-           long hval, disp;
+           u_int32_t fcode;
+           int hval, disp;
 
            fcode = BSD_KEY(oldcode,finchar);
            hval = BSD_HASH(oldcode,finchar,db->hshift);
@@ -959,7 +1072,8 @@ bsd_decompress(state, cmsg, hdroff)
                } while (dictp->codem1 < max_ent);
            }
 
-           /* Invalidate previous hash table entry
+           /*
+            * Invalidate previous hash table entry
             * assigned this code, and then take it over
             */
            dictp2 = &db->dict[max_ent+1];
@@ -983,20 +1097,23 @@ bsd_decompress(state, cmsg, hdroff)
     }
     dmsg->b_wptr = wptr;
 
-    /* fail on packets with bad lengths/sequence numbers */
-    if (dlen != 0) {
-       freemsg(dmsg);
-       return 0;
-    }
-
-    /* Keep the checkpoint right so that incompressible packets
+    /*
+     * Keep the checkpoint right so that incompressible packets
      * clear the dictionary at the right times.
      */
+    db->bytes_out += ilen;
     db->in_count += explen;
     if (bsd_check(db) && db->debug) {
        printf("bsd_decomp%d: peer should have cleared dictionary\n",
               db->unit);
     }
 
-    return dmsg;
+    ++db->comp_count;
+    db->comp_bytes += ilen + BSD_OVHD;
+    ++db->uncomp_count;
+    db->uncomp_bytes += explen;
+
+    *dmpp = mret;
+    return DECOMP_OK;
 }
+#endif /* DO_BSD_COMPRESS */