root/usr/src/uts/common/io/ppp/spppcomp/deflate.c
/*
 * deflate.c - interface the zlib procedures for Deflate compression
 * and decompression (as used by gzip) to the PPP code.
 *
 * This version is for use with STREAMS in Solaris 2
 *
 * Copyright (c) 2001 by Sun Microsystems, Inc.
 * All rights reserved.
 *
 * Copyright (c) 1994 The Australian National University.
 * All rights reserved.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation is hereby granted, provided that the above copyright
 * notice appears in all copies.  This software is provided without any
 * warranty, express or implied. The Australian National University
 * makes no representations about the suitability of this software for
 * any purpose.
 *
 * IN NO EVENT SHALL THE AUSTRALIAN NATIONAL UNIVERSITY BE LIABLE TO ANY
 * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
 * THE AUSTRALIAN NATIONAL UNIVERSITY HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * THE AUSTRALIAN NATIONAL UNIVERSITY SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE AUSTRALIAN NATIONAL UNIVERSITY HAS NO
 * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
 * OR MODIFICATIONS.
 *
 * $Id: deflate.c,v 1.9 1999/01/19 23:58:35 paulus Exp $
 */

#define NO_DUMMY_DECL

#include <sys/param.h>
#include <sys/types.h>
#include <sys/kmem.h>
#include <sys/stream.h>
#include <sys/cmn_err.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/errno.h>
#include <net/ppp_defs.h>

/* Defined for platform-neutral include file */
#define PACKETPTR       mblk_t *
#include <net/ppp-comp.h>
#include "s_common.h"
#include "zlib.h"

#if DO_DEFLATE

/*
 * State for a Deflate (de)compressor.
 */
struct deflate_state {
        int             seqno;
        int             w_size;
        int             unit;
        int             hdrlen;
        int             mru;
        int             flags;
        z_stream        strm;
        struct compstat stats;
};

#define DEFLATE_OVHD    2               /* Deflate overhead/packet */

#define DS_DEBUG        0x0001
#define DS_TESTIN       0x0002
#define DS_TESTOUT      0x0004

static void     *z_alloc(void *, uint_t items, uint_t size);
static void     z_free(void *, void *ptr);
static void     *z_comp_alloc(uchar_t *options, int opt_len);
static void     *z_decomp_alloc(uchar_t *options, int opt_len);
static void     z_comp_free(void *state);
static void     z_decomp_free(void *state);
static int      z_comp_init(void *state, uchar_t *options, int opt_len,
                        int unit, int hdrlen, int debug);
static int      z_decomp_init(void *state, uchar_t *options, int opt_len,
                        int unit, int hdrlen, int mru, int debug);
static int      z_compress(void *state, mblk_t **mret,
                        mblk_t *mp, int slen, int maxolen);
static int      z_incomp(void *state, mblk_t *dmsg);
static int      z_decompress(void *state, mblk_t **dmpp);
static void     z_comp_reset(void *state);
static void     z_decomp_reset(void *state);
static void     z_comp_stats(void *state, struct compstat *stats);
static int      z_set_effort(void *xstate, void *rstate, int effortlevel);

/*
 * Procedures exported to ppp_comp.c.
 */
struct compressor ppp_deflate = {
        CI_DEFLATE,             /* compress_proto */
        z_comp_alloc,           /* comp_alloc */
        z_comp_free,            /* comp_free */
        z_comp_init,            /* comp_init */
        z_comp_reset,           /* comp_reset */
        z_compress,             /* compress */
        z_comp_stats,           /* comp_stat */
        z_decomp_alloc,         /* decomp_alloc */
        z_decomp_free,          /* decomp_free */
        z_decomp_init,          /* decomp_init */
        z_decomp_reset,         /* decomp_reset */
        z_decompress,           /* decompress */
        z_incomp,               /* incomp */
        z_comp_stats,           /* decomp_stat */
        z_set_effort,           /* set_effort */
};

struct compressor ppp_deflate_draft = {
        CI_DEFLATE_DRAFT,       /* compress_proto */
        z_comp_alloc,           /* comp_alloc */
        z_comp_free,            /* comp_free */
        z_comp_init,            /* comp_init */
        z_comp_reset,           /* comp_reset */
        z_compress,             /* compress */
        z_comp_stats,           /* comp_stat */
        z_decomp_alloc,         /* decomp_alloc */
        z_decomp_free,          /* decomp_free */
        z_decomp_init,          /* decomp_init */
        z_decomp_reset,         /* decomp_reset */
        z_decompress,           /* decompress */
        z_incomp,               /* incomp */
        z_comp_stats,           /* decomp_stat */
        z_set_effort,           /* set_effort */
};

#define DECOMP_CHUNK    512

/*
 * Space allocation and freeing routines for use by zlib routines.
 */
struct zchunk {
        uint_t          size;
        uint_t          guard;
};

#define GUARD_MAGIC     0x77a6011a

/*
 * z_alloc()
 */
/* ARGSUSED */
static void *
z_alloc(void *notused, uint_t items, uint_t size)
{
        struct zchunk   *z;

        size = items * size + sizeof (struct zchunk);

        z = (struct zchunk *)kmem_alloc(size, KM_NOSLEEP);
        if (z == NULL)
                return (NULL);

        z->size = size;
        z->guard = GUARD_MAGIC;

        return ((void *)(z + 1));
}

/*
 * z_free()
 */
/* ARGSUSED */
static void
z_free(void *notused, void *ptr)
{
        struct zchunk   *z = ((struct zchunk *)ptr) - 1;

        if (ptr == NULL)
                return;

        if (z->guard != GUARD_MAGIC) {
                cmn_err(CE_CONT,
                    "deflate: z_free of corrupted chunk at 0x%p (%x, %x)\n",
                    (void *)z, z->size, z->guard);

                return;
        }

        kmem_free(z, z->size);
}

/*
 * Allocate space for a compressor.
 */
static void *
z_comp_alloc(uchar_t *options, int opt_len)
{
        struct deflate_state    *state;
        int                     w_size;

        if (opt_len != CILEN_DEFLATE ||
                (options[0] != CI_DEFLATE && options[0] != CI_DEFLATE_DRAFT) ||
                options[1] != CILEN_DEFLATE ||
                DEFLATE_METHOD(options[2]) != DEFLATE_METHOD_VAL ||
                options[3] != DEFLATE_CHK_SEQUENCE) {

                return (NULL);
        }

        w_size = DEFLATE_SIZE(options[2]);
        /*
         * Check <= minimum size to avoid unfixable zlib bug -- window size
         * 256 (w_size 8) is not supported.
         */
        if (w_size <= DEFLATE_MIN_SIZE || w_size > DEFLATE_MAX_SIZE) {
                return (NULL);
        }

        state = (struct deflate_state *)kmem_zalloc(sizeof (*state), KM_SLEEP);
        ASSERT(state != NULL);

        state->strm.zalloc = (alloc_func)z_alloc;
        state->strm.zfree = (free_func)z_free;

        if (deflateInit2(&state->strm, Z_DEFAULT_COMPRESSION,
                DEFLATE_METHOD_VAL, -w_size, 8, Z_DEFAULT_STRATEGY) != Z_OK) {

                kmem_free(state, sizeof (*state));

                return (NULL);
        }

        state->w_size = w_size;

        bzero(&state->stats, sizeof (state->stats));

        return ((void *)state);
}

/*
 * z_comp_free()
 */
static void
z_comp_free(void *arg)
{
        struct deflate_state    *state = (struct deflate_state *)arg;

        (void) deflateEnd(&state->strm);

        kmem_free(state, sizeof (*state));
}

/*
 * z_comp_init()
 */
static int
z_comp_init(void *arg, uchar_t *options, int opt_len, int unit, int hdrlen,
        int debug)
{
        struct deflate_state *state = (struct deflate_state *)arg;

        if (opt_len < CILEN_DEFLATE ||
                (options[0] != CI_DEFLATE && options[0] != CI_DEFLATE_DRAFT) ||
                options[1] != CILEN_DEFLATE ||
                DEFLATE_METHOD(options[2]) != DEFLATE_METHOD_VAL ||
                DEFLATE_SIZE(options[2]) != state->w_size ||
                options[3] != DEFLATE_CHK_SEQUENCE) {

                return (0);
        }

        state->seqno = 0;
        state->unit = unit;
        state->hdrlen = hdrlen;
        if (debug)
                state->flags |= DS_DEBUG;
        else
                state->flags &= ~DS_DEBUG;

        (void) deflateReset(&state->strm);

        return (1);
}

/*
 * z_comp_reset()
 */
static void
z_comp_reset(void *arg)
{
        struct deflate_state    *state = (struct deflate_state *)arg;

        state->seqno = 0;

        (void) deflateReset(&state->strm);
}

/*
 * z_compress()
 */
static int
z_compress(void *arg, mblk_t **mret, mblk_t *mp, int orig_len, int maxolen)
{
        struct deflate_state    *state = (struct deflate_state *)arg;
        uchar_t                 *rptr, *rmax;
        uchar_t                 *wptr;
        int                     olen;
        int                     wspace;
        int                     r;
        int                     flush;
        mblk_t                  *m;
#if defined(lint) || defined(_lint)
        uchar_t                 hdlcaddr, hdlcctrl;
#else
        int                     hdlcaddr, hdlcctrl;
#endif

#define ADJRPTR() {                                             \
        if (rptr != NULL) {                                     \
                while (rptr >= rmax) {                          \
                        if ((mp = mp->b_cont) == NULL) {        \
                                rptr = NULL;                    \
                                break;                          \
                        }                                       \
                        rptr = mp->b_rptr;                      \
                        rmax = mp->b_wptr;                      \
                }                                               \
        }                                                       \
}

#define GETBYTE(v) {                                            \
        if (rptr != NULL) {                                     \
                (v) = *rptr++;                                  \
        }                                                       \
}

        /*
         * Check that the protocol is one we handle.  Pullup is *NOT*
         * possible here.
         */
        *mret = NULL;
        rptr = mp->b_rptr;
        rmax = mp->b_wptr;
        ADJRPTR();
        GETBYTE(hdlcaddr);
        ADJRPTR();
        GETBYTE(hdlcctrl);
        ADJRPTR();

        /*
         * Per RFC 1979, the protocol field must be compressed using a
         * PFC-like procedure.  Also, all protocols between 0000-3FFF
         * except the two compression protocols must be LZ compressed.
         */
        if (rptr == NULL)
                return (orig_len);
        r = *rptr;
        if (r == 0) {
                rptr++;
                ADJRPTR();
                if (rptr == NULL || *rptr == PPP_COMP || *rptr == PPP_COMPFRAG)
                        return (orig_len);
        } else {
                if (r > 0x3F)
                        return (orig_len);
        }

        /*
         * Allocate one mblk initially
         */
        if (maxolen > orig_len) {
                maxolen = orig_len;
        }

        if (maxolen <= PPP_HDRLEN + 2) {
                wspace = 0;
                m = NULL;
        } else {
                wspace = maxolen + state->hdrlen;
                if (wspace > 4096) {
                        wspace = 4096;
                }

                m = allocb(wspace, BPRI_MED);
        }

        if (m != NULL) {

                wspace = m->b_datap->db_lim - m->b_wptr;

                *mret = m;

                if (state->hdrlen + PPP_HDRLEN + 2 < wspace) {
                        m->b_rptr += state->hdrlen;
                        m->b_wptr = m->b_rptr;
                        wspace -= state->hdrlen;
                }

                wptr = m->b_wptr;

                /*
                 * Copy over the PPP header and store the 2-byte
                 * sequence number
                 */
                wptr[0] = hdlcaddr;
                wptr[1] = hdlcctrl;
                wptr[2] = PPP_COMP >> 8;
                wptr[3] = PPP_COMP;

                wptr += PPP_HDRLEN;

                wptr[0] = state->seqno >> 8;
                wptr[1] = state->seqno;
                wptr += 2;

#ifdef DEBUG
                /*
                 * If testing output, just garbling the sequence here
                 * does the trick.
                 */
                if ((state->flags & DS_TESTOUT) && (state->seqno % 100) == 50)
                        wptr[-1] ^= 0xAA;
#endif

                state->strm.next_out = wptr;
                state->strm.avail_out = wspace - (PPP_HDRLEN + 2);
        } else {
                state->strm.next_out = NULL;
                state->strm.avail_out = 1000000;
        }

        ++state->seqno;

        state->strm.next_in = rptr;
        state->strm.avail_in = mp->b_wptr - rptr;

        olen = 0;

        for (;;) {
                flush = (mp == NULL || mp->b_cont == NULL) ? Z_PACKET_FLUSH :
                    Z_NO_FLUSH;
                r = deflate(&state->strm, flush);

                if (r != Z_OK) {
                        cmn_err(CE_CONT,
                            "z_compress%d: deflate returned %d (%s)\n",
                            state->unit, r,
                            (state->strm.msg? state->strm.msg: ""));

                        break;
                }

                if (state->strm.avail_in == 0) {
                        if (mp != NULL)
                                mp = mp->b_cont;
                        if (mp == NULL) {
                                if (state->strm.avail_out != 0)
                                        break;  /* all done */
                        } else {
                                state->strm.next_in = mp->b_rptr;
                                state->strm.avail_in = mp->b_wptr - mp->b_rptr;
                        }
                }

                if (state->strm.avail_out == 0) {
                        if (m != NULL) {
                                m->b_wptr += wspace;
                                olen += wspace;
                                wspace = maxolen - olen;

                                if (wspace <= 0) {
                                        wspace = 0;
                                        m->b_cont = NULL;
                                } else {
                                        if (wspace < 32) {
                                                wspace = 32;
                                        } else if (wspace > 4096) {
                                                wspace = 4096;
                                        }

                                        m->b_cont = allocb(wspace, BPRI_MED);
                                }

                                m = m->b_cont;

                                if (m != NULL) {
                                        state->strm.next_out = m->b_wptr;
                                        wspace = m->b_datap->db_lim -
                                            m->b_wptr;
                                        state->strm.avail_out = wspace;
                                }
                        }

                        if (m == NULL) {
                                state->strm.next_out = NULL;
                                state->strm.avail_out = 1000000;
                        }
                }
        }

        if (m != NULL) {
                m->b_wptr += wspace - state->strm.avail_out;
                olen += wspace - state->strm.avail_out;
        }

        /*
         * See if we managed to reduce the size of the packet.
         */
        if (olen < orig_len && m != NULL) {
                state->stats.comp_bytes += olen;
                state->stats.comp_packets++;
        } else {
                if (*mret != NULL) {
                        freemsg(*mret);
                        *mret = NULL;
                }

                state->stats.inc_bytes += orig_len;
                state->stats.inc_packets++;

                olen = orig_len;
        }

        state->stats.unc_bytes += orig_len;
        state->stats.unc_packets++;

        return (olen);
}

/*
 * z_incomp()
 *
 * Incompressible data has arrived - add it to the history.
 */
static int
z_incomp(void *arg, mblk_t *mp)
{
        struct deflate_state    *state = (struct deflate_state *)arg;
        uchar_t                 *rptr, *rmax;
        int                     rlen;
        int                     r;

        /*
         * Check that the protocol is one we handle.  Pullup is *NOT*
         * possible here.
         */
        rptr = mp->b_rptr;
        rmax = mp->b_wptr;
        ADJRPTR();
        rptr++;         /* skip address */
        ADJRPTR();
        rptr++;         /* skip control */
        ADJRPTR();

        /*
         * Per RFC 1979, the protocol field must be compressed using a
         * PFC-like procedure.  Also, all protocols between 0000-3FFF
         * except the two compression protocols must be LZ compressed.
         */
        if (rptr == NULL)
                return (0);
        r = *rptr;
        if (r == 0) {
                rptr++;
                ADJRPTR();
                if (rptr == NULL || *rptr == PPP_COMP || *rptr == PPP_COMPFRAG)
                        return (0);
        } else {
                if (r > 0x3F)
                        return (0);
        }

        ++state->seqno;

        /*
         * Iterate through the message blocks, adding the characters
         * in them to the decompressor's history.
         */
        rlen = mp->b_wptr - rptr;

        state->strm.next_in = rptr;
        state->strm.avail_in = rlen;

        for (;;) {
                r = inflateIncomp(&state->strm);

                if (r != Z_OK) {        /* gak! */
                        if (state->flags & DS_DEBUG) {
                                cmn_err(CE_CONT,
                                    "z_incomp%d: inflateIncomp returned "
                                    "%d (%s)\n", state->unit, r,
                                    (state->strm.msg? state->strm.msg: ""));
                        }

                        return (-1);
                }

                mp = mp->b_cont;
                if (mp == NULL) {
                        break;
                }

                state->strm.next_in = mp->b_rptr;
                state->strm.avail_in = mp->b_wptr - mp->b_rptr;

                rlen += state->strm.avail_in;
        }

        /*
         * Update stats
         */
        state->stats.inc_bytes += rlen;
        state->stats.inc_packets++;
        state->stats.unc_bytes += rlen;
        state->stats.unc_packets++;
        return (0);
#undef ADJRPTR
}

/*
 * z_comp_stats()
 */
static void
z_comp_stats(void *arg, struct compstat *stats)
{
        struct deflate_state    *state = (struct deflate_state *)arg;
        uint_t                  out;

        *stats = state->stats;
        stats->ratio = stats->unc_bytes;
        out = stats->comp_bytes + stats->unc_bytes;

        if (stats->ratio <= 0x7ffffff) {
                stats->ratio <<= 8;
        } else {
                out >>= 8;
        }

        if (out != 0) {
                stats->ratio /= out;
        }
}

/*
 * z_decomp_alloc()
 *
 * Allocate space for a decompressor.
 */
static void *
z_decomp_alloc(uchar_t *options, int opt_len)
{
        struct deflate_state    *state;
        int                     w_size;

        if (opt_len != CILEN_DEFLATE ||
                (options[0] != CI_DEFLATE && options[0] != CI_DEFLATE_DRAFT) ||
                options[1] != CILEN_DEFLATE ||
                DEFLATE_METHOD(options[2]) != DEFLATE_METHOD_VAL ||
                options[3] != DEFLATE_CHK_SEQUENCE) {

                return (NULL);
        }

        w_size = DEFLATE_SIZE(options[2]);
        /*
         * Check <= minimum size to avoid unfixable zlib bug -- window size
         * 256 (w_size 8) is not supported.
         */
        if (w_size <= DEFLATE_MIN_SIZE || w_size > DEFLATE_MAX_SIZE) {
                return (NULL);
        }

        state = (struct deflate_state *)kmem_zalloc(sizeof (*state), KM_SLEEP);
        ASSERT(state != NULL);

        state->strm.zalloc = (alloc_func)z_alloc;
        state->strm.zfree = (free_func)z_free;

        if (inflateInit2(&state->strm, -w_size) != Z_OK) {
                kmem_free(state, sizeof (*state));
                return (NULL);
        }

        state->w_size = w_size;

        bzero(&state->stats, sizeof (state->stats));

        return ((void *)state);
}

/*
 * z_decomp_free()
 */
static void
z_decomp_free(void *arg)
{
        struct deflate_state    *state = (struct deflate_state *)arg;

        (void) inflateEnd(&state->strm);

        kmem_free(state, sizeof (*state));
}

/*
 * z_decomp_init()
 */
static int
z_decomp_init(void *arg, uchar_t *options, int opt_len, int unit, int hdrlen,
        int mru, int debug)
{
        struct deflate_state *state = (struct deflate_state *)arg;

        if (opt_len < CILEN_DEFLATE ||
                (options[0] != CI_DEFLATE && options[0] != CI_DEFLATE_DRAFT) ||
                options[1] != CILEN_DEFLATE ||
                DEFLATE_METHOD(options[2]) != DEFLATE_METHOD_VAL ||
                DEFLATE_SIZE(options[2]) != state->w_size ||
                options[3] != DEFLATE_CHK_SEQUENCE) {

                return (0);
        }

        state->seqno = 0;
        state->unit = unit;
        state->hdrlen = hdrlen;
        if (debug)
                state->flags |= DS_DEBUG;
        else
                state->flags &= ~DS_DEBUG;
        state->mru = mru;

        (void) inflateReset(&state->strm);

        return (1);
}

/*
 * z_decomp_reset()
 */
static void
z_decomp_reset(void *arg)
{
        struct deflate_state    *state = (struct deflate_state *)arg;

        state->seqno = 0;

        (void) inflateReset(&state->strm);
}

/*
 * z_decompress()
 *
 * Decompress a Deflate-compressed packet.
 *
 * 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 int
z_decompress(void *arg, mblk_t **mop)
{
        struct deflate_state    *state = (struct deflate_state *)arg;
        mblk_t                  *mi = *mop, *mnext;
        mblk_t                  *mo;
        mblk_t                  *mo_head;
        uchar_t                 *rptr, *rmax;
        uchar_t                 *wptr;
        int                     rlen;
        int                     olen;
        int                     ospace;
        int                     seq;
        int                     flush;
        int                     r;
        int                     decode_proto;
#if defined(lint) || defined(_lint)
        uchar_t                 hdlcaddr, hdlcctrl;
#else
        int                     hdlcaddr, hdlcctrl;
#endif

        /* Note: spppcomp already did a pullup to fix the first buffer. */
        *mop = NULL;
        rptr = mi->b_rptr + PPP_HDRLEN;
        rmax = mi->b_wptr;
        if (rptr > rmax) {
                if (state->flags & DS_DEBUG) {
                        cmn_err(CE_CONT, "z_decompress%d: bad buffer\n",
                            state->unit);
                }
                freemsg(mi);
                return (DECOMP_ERROR);
        }

        hdlcaddr = rptr[-PPP_HDRLEN];
        hdlcctrl = rptr[-PPP_HDRLEN+1];

        /*
         * Note that we free as we go.  If we fail to decompress,
         * there's nothing good that the caller can do.
         */
#define ADJRPTR() {                                             \
        if (rptr != NULL) {                                     \
                while (rptr >= rmax) {                          \
                        mnext = mi->b_cont;                     \
                        freeb(mi);                              \
                        if ((mi = mnext) == NULL) {             \
                                rptr = NULL;                    \
                                break;                          \
                        }                                       \
                        rptr = mi->b_rptr;                      \
                        rmax = mi->b_wptr;                      \
                }                                               \
        }                                                       \
}

        /*
         * Check the sequence number
         */
        ADJRPTR();
        seq = rptr == NULL ? 0 : (*rptr++ << 8);
        ADJRPTR();
        if (rptr == NULL) {
                if (state->flags & DS_DEBUG) {
                        cmn_err(CE_CONT, "z_decompress%d: bad buffer\n",
                            state->unit);
                }
                return (DECOMP_ERROR);
        }

        seq |= *rptr++;

#ifdef DEBUG
        /*
         * If testing input, just pretending the sequence is bad here
         * does the trick.
         */
        if ((state->flags & DS_TESTIN) && (state->seqno % 300) == 101)
                seq ^= 0x55;
#endif
        if (seq != state->seqno++) {
                freemsg(mi);
                if (state->flags & DS_DEBUG) {
                        cmn_err(CE_CONT,
                                "z_decompress%d: bad seq # %d, expected %d\n",
                                state->unit, seq, state->seqno - 1);
                }
                return (DECOMP_ERROR);
        }

        /*
         * Allocate an output message block
         */
        mo = allocb(DECOMP_CHUNK + state->hdrlen, BPRI_MED);
        if (mo == NULL) {
                freemsg(mi);
                return (DECOMP_ERROR);
        }

        mo_head = mo;
        mo->b_cont = NULL;
        mo->b_rptr += state->hdrlen;
        mo->b_wptr = wptr = mo->b_rptr;

        ospace = DECOMP_CHUNK;
        olen = 0;

        /*
         * Fill in the first part of the PPP header.  The protocol field
         * comes from the decompressed data.
         */
        *wptr++ = hdlcaddr;
        *wptr++ = hdlcctrl;
        *wptr++ = 0;

        /*
         * Set up to call inflate.  We set avail_out to 1 initially so we can
         * look at the first byte of the output and decide whether we have
         * a 1-byte or 2-byte protocol field.
         */
        state->strm.next_in = rptr;
        state->strm.avail_in = mi->b_wptr - rptr;

        rlen = state->strm.avail_in + PPP_HDRLEN + DEFLATE_OVHD;

        state->strm.next_out = wptr;
        state->strm.avail_out = 1;

        decode_proto = 1;

        /*
         * Call inflate, supplying more input or output as needed.
         */
        for (;;) {

                flush = (mi == NULL || mi->b_cont == NULL) ?
                    Z_PACKET_FLUSH : Z_NO_FLUSH;
                r = inflate(&state->strm, flush);

                if (r != Z_OK) {

                        if (state->flags & DS_DEBUG) {
                                cmn_err(CE_CONT,
                                    "z_decompress%d: inflate returned %d "
                                    "(%s)\n", state->unit, r,
                                    (state->strm.msg? state->strm.msg: ""));
                        }

                        if (mi != NULL)
                                freemsg(mi);
                        freemsg(mo_head);

                        return (DECOMP_FATALERROR);
                }

                if (state->strm.avail_in == 0) {
                        if (mi != NULL) {
                                mnext = mi->b_cont;
                                freeb(mi);
                                mi = mnext;
                        }
                        if (mi == NULL) {
                                if (state->strm.avail_out != 0)
                                        break;  /* all done */
                        } else {
                                state->strm.next_in = mi->b_rptr;
                                state->strm.avail_in = mi->b_wptr - mi->b_rptr;

                                rlen += state->strm.avail_in;
                        }
                }

                if (state->strm.avail_out == 0) {
                        if (decode_proto) {
                                state->strm.avail_out = ospace - PPP_HDRLEN;

                                if ((wptr[0] & 1) == 0) {
                                        /*
                                         * 2-byte protocol field
                                         */
                                        wptr[-1] = wptr[0];

                                        --state->strm.next_out;
                                        ++state->strm.avail_out;
                                }

                                decode_proto = 0;
                        } else {
                                mo->b_wptr += ospace;
                                olen += ospace;

                                mo->b_cont = allocb(DECOMP_CHUNK, BPRI_MED);

                                mo = mo->b_cont;
                                if (mo == NULL) {
                                        if (mi != NULL)
                                                freemsg(mi);
                                        freemsg(mo_head);
                                        return (DECOMP_ERROR);
                                }

                                state->strm.next_out = mo->b_rptr;
                                state->strm.avail_out = ospace = DECOMP_CHUNK;
                        }
                }
        }

        if (decode_proto) {
                freemsg(mo_head);
                return (DECOMP_ERROR);
        }

        mo->b_wptr += ospace - state->strm.avail_out;
        olen += ospace - state->strm.avail_out;

        if ((olen > state->mru + PPP_HDRLEN) && (state->flags & DS_DEBUG)) {
                cmn_err(CE_CONT, "z_decompress%d: exceeded mru (%d > %d)\n",
                    state->unit, olen, state->mru + PPP_HDRLEN);
        }

        state->stats.unc_bytes += olen;
        state->stats.unc_packets++;
        state->stats.comp_bytes += rlen;
        state->stats.comp_packets++;

        *mop = mo_head;

        return (DECOMP_OK);
}

/* ARGSUSED */
static int
z_set_effort(void *xarg, void *rarg, int effortlevel)
{
        struct deflate_state *xstate = (struct deflate_state *)xarg;
#ifdef DEBUG
        struct deflate_state *rstate = (struct deflate_state *)rarg;
#endif
        int retv;

#ifdef DEBUG
        if (effortlevel == 42 || effortlevel == 2112) {
                /* corrupt received data. */
                if (rstate != NULL) {
                        rstate->flags |= DS_TESTIN;
                        cmn_err(CE_CONT, "deflate: enabled input testing.");
                }
                if (effortlevel != 2112)
                        return (0);
        }
        if (effortlevel == 2001 || effortlevel == 2112) {
                /* corrupt transmitted data. */
                if (xstate != NULL) {
                        xstate->flags |= DS_TESTOUT;
                        cmn_err(CE_CONT, "deflate: enabled output testing.");
                }
                return (0);
        }
#endif
        if (effortlevel < -1 || effortlevel > 9)
                return (EINVAL);
        if (xstate == NULL)
                return (0);
        retv = deflateParams(&xstate->strm, effortlevel, Z_DEFAULT_STRATEGY);
        return (retv == Z_OK ? 0 : EINVAL);
}

#endif /* DO_DEFLATE */