/*
 * This file was originally slcompress.c.  It has been generalized
 * to be a streams module.  It is felt that the overhead of
 * a module should be minimal on a per packet basis.  Also,
 * this module can be reused with both PPP and SLIP async
 * modules.
 */

/*
 * Routines to compress and uncompress tcp packets (for transmission
 * over low speed serial lines.
 *
 * Copyright (c) 1989 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by the University of California, Berkeley.  The name of the
 * University may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 *	Van Jacobson (van@helios.ee.lbl.gov), Dec 31, 1989:
 *	- Initial distribution.
 *
 * Copyright (C) 1993 Purdue University, All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by Purdue University.  The name of the University may not be used
 * to endorse or promote products derived from this software without
 * specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Note: this copyright applies to portions of this software developed
 * at Purdue beyond the software covered by the original copyright.
 */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stream.h>
#include <sys/stropts.h>

#ifdef	SOLARIS1
#include <sys/user.h>
#include <sys/kmem_alloc.h>
#else
#include <sys/kmem.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#endif
#include <sys/errno.h>

#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>


#include "ppp.h"
#include "vjc.h"
#include "dpio.h"

#ifdef	DEBUGS
#include <sys/syslog.h>
#define	static	
#ifdef	SOLARIS1
#define	DLOG(s)		if(vjc_debug) log(LOG_INFO, (s))
#define	DLOG1(s, a)	if(vjc_debug) log(LOG_INFO, (s), (a))
#define	DLOG2(s, a, b)	if(vjc_debug) log(LOG_INFO, (s), (a), (b))
#else
#define	DLOG(s)		if(vjc_debug) cmn_err(CE_CONT, (s))
#define	DLOG1(s, a)	if(vjc_debug) cmn_err(CE_CONT, (s), (a))
#define	DLOG2(s, a, b)	if(vjc_debug) cmn_err(CE_CONT, (s), (a), (b))
#endif

int	vjc_debug = 0;
#else
#define	DLOG(s)		{}
#define	DLOG1(s, a)	{}
#define	DLOG2(s, a, b)	{}
#endif

#ifndef VJC_NO_STATS
#define VJCINCR(counter) ++vjc->stats.counter;
#else
#define VJCINCR(counter)
#endif

#define VJCBCMP(p1, p2, n) bcmp((char *)(p1), (char *)(p2), (int)(n))
#define VJCBCOPY(p1, p2, n) bcopy((char *)(p1), (char *)(p2), (int)(n))

#ifndef KERNEL
#define ovbcopy bcopy
#endif

#ifdef	SOLARIS1
/*
 * Define a number of facilities to allow the code to look more
 * like Solaris 2.X.
 */
#define	qprocson(x)
#define	qprocsoff(x)
#define	canputnext(q)	(canput((q)->q_next))
#define	KM_SLEEP	KMEM_SLEEP
#define	KM_NOSLEEP	KMEM_NOSLEEP
#endif

#define	MIN_MSG_DATA(mp, len)	\
		((mp)->b_wptr - (mp)->b_rptr >= (len) || pullupmsg((mp), (len)))
#define	PPP_HDR_SIZE	(sizeof(struct ppp_header))

#ifdef	__STDC__
static int vjcopen(queue_t *, dev_t *, int, int, cred_t *);
static int vjcclose(queue_t *, cred_t *);
static int vjcrput(queue_t *, mblk_t *);
static int vjcrsrv(queue_t *);
static int vjcwput(queue_t *, mblk_t *);
static int vjcwsrv(queue_t *);
#else
static int vjcopen(/* queue_t *, dev_t *, int, int, cred_t * */);
static int vjcclose(/* queue_t *, cred_t * */);
static int vjcrput(/* queue_t *, mblk_t * */);
static int vjcrsrv(/* queue_t * */);
static int vjcwput(/* queue_t *, mblk_t * */);
static int vjcwsrv(/* queue_t * */);
#endif

#ifdef	__STDC__
static void vjc_tinit(vjc_t *);
static void vjc_rinit(vjc_t *);
static mblk_t *compress_tcp(mblk_t *, vjc_t *, int);
static mblk_t *uncompress_tcp(mblk_t *, vjc_t *, int);
#else
static void vjc_tinit(/* vjc_t * */);
static void vjc_rinit(/* vjc_t * */);
static mblk_t *compress_tcp(/* mblk_t *, vjc_t *, int */);
static mblk_t *uncompress_tcp(/* mblk_t *, vjc_t *, int */);
#endif

static struct module_info vjc_info = {
    0xcece, "vjc", 0, INFPSZ, 16384, 4096
};
static struct qinit vjcrinit = {
    vjcrput, vjcrsrv, vjcopen, vjcclose, NULL, &vjc_info, NULL
};
static struct qinit vjcwinit = {
    vjcwput, vjcwsrv, NULL, NULL, NULL, &vjc_info, NULL
};

struct streamtab vjcinfo = {
    &vjcrinit, &vjcwinit, NULL, NULL
};

static vjc_t *vjcs;
static kmutex_t vjcs_lock;

#ifdef	SOLARIS1
static int
vjcopen(rq, dev, flag, sflag)
queue_t *rq;
dev_t dev;
int flag,
    sflag;
#else
static int
vjcopen(rq, devp, flag, sflag, credp)
queue_t *rq;
dev_t *devp;
int flag,
    sflag;
cred_t *credp;
#endif
{
    int	s;
    vjc_t *vjc;
    DLOG("vjcopen\n");
    /*
     * Not the first open, everything is already allocated.
     */
    if (rq->q_ptr)
	return 0;

#ifdef	SOLARIS1
    s = splstr();
#endif
    if ((vjc = (vjc_t *)kmem_zalloc(sizeof(vjc_t), KM_SLEEP)) == NULL) {
#ifdef	SOLARIS1
	splx(s);
	u.u_error = ENOMEM;
	return OPENFAIL;
#else
	return ENOMEM;
#endif
    }

    mutex_enter(&vjcs_lock);
    vjc->next_vjc = vjcs;
    vjcs = vjc;
    mutex_exit(&vjcs_lock);
    rq->q_ptr = WR(rq)->q_ptr = (caddr_t)vjc;
    mutex_init(&vjc->tlock, "vjc module transmit lock",
	       MUTEX_DRIVER, (void *)0);
    mutex_init(&vjc->rlock, "vjc module recieve lock",
	       MUTEX_DRIVER, (void *)0);
    qprocson(rq);
#ifdef	SOLARIS1
    splx(s);
#endif
    return 0;
}

#ifdef	SOLARIS1
static int
vjcclose(rq)
queue_t *rq;
#else
static int
vjcclose(rq, credp)
queue_t *rq;
cred_t *credp;
#endif
{
    vjc_t *vjc, **pvjc;
#ifdef	SOLARIS1
    int	s = splstr();
#endif

    DLOG("vjcclose\n");
    qprocsoff(rq);

    mutex_enter(&vjcs_lock);
    for (pvjc = &vjcs ; vjc = *pvjc ; pvjc = &vjc->next_vjc)
	if (vjc == (vjc_t *)rq->q_ptr)
	    break;
    *pvjc = vjc->next_vjc;
    mutex_exit(&vjcs_lock);

    /*
     * Free associated memory
     */
    (void)vjc_tcpenable(vjc, 0);
    mutex_destroy(&vjc->rlock);
    mutex_destroy(&vjc->tlock);
    kmem_free((caddr_t)vjc, sizeof(vjc_t));

    rq->q_ptr = WR(rq)->q_ptr = (caddr_t)0;
#ifdef	SOLARIS1
    splx(s);
#endif
    return 0;
}

/*
 * Data coming up from interface..
 */
static int
vjcrput(q, mp)
queue_t *q;
mblk_t *mp;
{
    /*
     * Handle some high priority messages
     */
    switch (mp->b_datap->db_type) {
     case M_FLUSH:
	if (*mp->b_rptr & FLUSHR) {
	    flushq(q, FLUSHDATA);
	    vjc_rinit((vjc_t *)q->q_ptr);
	}
	break;
    }
    if (queclass(mp) == QPCTL)
	putnext(q, mp);
    else
	putq(q, mp);
}


static int
vjcrsrv(q)
queue_t *q;
{
    mblk_t *pmp;
    vjc_t *vjc = (vjc_t *)q->q_ptr;
    u_short proto;
    struct ppp_header *ph;
    int mlen;

    while (pmp = getq(q)) {
	DLOG2("vjcrsrv: %o %d\n", pmp->b_datap->db_type, msgdsize(pmp));
	/*
	 * Honor flow control.
	 */
	if (!canputnext(q)) {
	    putbq(q, pmp);
	    return;
	}
	switch (pmp->b_datap->db_type) {
	 case M_DATA:
	    break;
	 case M_CTL:
	    switch (*(u_char *)pmp->b_rptr) {
	     case DP_IF_IERROR:
		vjc->flags |= VJCF_TOSS;
		break;
	    }
	    /* Drop Through */
	 default:
	    goto notcompressed;
	}
	/*
	 * Look for valid PPP packets
	 */
	if (!(vjc->flags & VJCF_COMPTCP) ||
	    pmp->b_datap->db_type != M_DATA)
	    goto notcompressed;
	
	mlen = msgdsize(pmp);
	if (mlen < PPP_HDR_SIZE)
	    goto bad_fmt;
	if (!MIN_MSG_DATA(pmp, PPP_HDR_SIZE)) {
	  out_of_mem:
	    freemsg(pmp);
	    putctl1(q, M_CTL, DP_IF_IERROR);
	    continue;
	}

	/*
	 * Examine the PPP Header
	 */
	ph = (struct ppp_header *)pmp->b_rptr;
	DLOG1("vjcrsrv: before proto %x\n", ntohs(ph->ph_protocol));
	switch (proto = ntohs(ph->ph_protocol)) {
	 case PPP_IP:
	 default:
	    break;
	 case PPP_VJC_UNCOMP:
	 case PPP_VJC_COMP:
	    switch (vjc_pullup_tcp(pmp, mlen)) {
	     case VPT_NO_MEM:
		goto out_of_mem;
	     case VPT_SUCCESS:
		if (proto != PPP_IP &&
		    !(pmp = uncompress_tcp(pmp, vjc, mlen))) {
		    putctl1(q, M_CTL, DP_IF_IERROR);
		    continue;
		}
		break;
	     case VPT_BAD_FMT:
	     bad_fmt:
		vjc->flags |= VJCF_TOSS;
	     case VPT_NOT_TCP:
		VJCINCR(errorin)
		break;
	    }
	}
	DLOG1("vjcrsrv: after  proto %x\n", ntohs(ph->ph_protocol));
      notcompressed:
	putnext(q, pmp);
    }
}

/*
 * Data to be sent out
 */
static int
vjcwput(q, mp)
queue_t *q;
mblk_t *mp;
{
    /*
     * Handle some high priority messages
     */
    switch (mp->b_datap->db_type) {
     case M_FLUSH:
	if (*mp->b_rptr & FLUSHR) {
	    vjc_t *vjc = (vjc_t *)q->q_ptr;
	    mutex_enter(&vjc->tlock);
	    flushq(q, FLUSHDATA);
	    vjc_tinit(vjc);
	    mutex_exit(&vjc->tlock);
	}
	break;
     case M_IOCTL:
	vjcioc(q, mp);
	return;
    }
    if (queclass(mp) == QPCTL)
	putnext(q, mp);
    else
	putq(q, mp);
}

static int
vjcwsrv(q)
queue_t *q;
{
    mblk_t *pmp;
    vjc_t *vjc = (vjc_t *)q->q_ptr;

    while (pmp = getq(q)) {
	DLOG2("vjcwsrv: %o %d\n", pmp->b_datap->db_type, msgdsize(pmp));
	/*
	 * Honor flow control.
	 */
	if (!canputnext(q)) {
	    putbq(q, pmp);
	    return;
	}
	/*
	 * Look for valid PPP packets
	 */
	if (!(vjc->flags & VJCF_COMPTCP) ||
	    pmp->b_datap->db_type != M_DATA)
	    goto not_tcp;
	
	switch (vjc_pullup_tcp(pmp, msgdsize(pmp))) {
	 case VPT_NO_MEM:
	    freemsg(pmp);
	    putctl1(OTHERQ(q), M_CTL, DP_IF_OERROR);
	    DLOG("vjcwsrv: no mem\n");
	    continue;
	 case VPT_SUCCESS:
	    DLOG1("vjcwsrv: before proto %x\n",
		 ntohs(((struct ppp_header *)pmp->b_rptr)->ph_protocol));
	    pmp = compress_tcp(pmp, vjc, 1);
	    DLOG1("vjcwsrv: after proto %x\n",
		 ntohs(((struct ppp_header *)pmp->b_rptr)->ph_protocol));
	    break;
	 case VPT_BAD_FMT:
	    VJCINCR(errorout)
	    vjc->last_xmit = VJC_MAX_STATES + 1;
	    DLOG("vjcwsrv: bad fmt\n");
	    break;
	 case VPT_NOT_TCP:
	 not_tcp:
	    DLOG("vjcwsrv: not tcp\n");
	    break;
	}
	putnext(q, pmp);
    }
}


static int
vjcioc(q, mp)
queue_t *q;
mblk_t *mp;
{
    register struct iocblk *ioc;
    register vjc_t *vjc;
    int err = 0;

    ioc = (struct iocblk *)mp->b_rptr;
    vjc = (vjc_t *)q->q_ptr;

    if (ioc->ioc_count == TRANSPARENT) {
	/*
	 * No TRANSPARENT ioctl support.
	 */
	DLOG1("transparent ioctl: %x\n", ioc->ioc_cmd);
	if (mp->b_cont)
	    DLOG2("transparent ioctl data?: 0x%x %x\n",
		  mp->b_cont->b_datap->db_type, msgdsize(mp->b_cont));
	err = EINVAL;
	goto vjc_ioc_ret;
    }
    switch (ioc->ioc_cmd) {
     /*
      * enable or disable VJ compression
      */
     case DPIOCSVJCOMP:
	if (ioc->ioc_count != sizeof(u_char)) {
	    err = EINVAL;
	    break;
	}
	err = vjc_tcpenable(vjc, *(u_char *)mp->b_cont->b_rptr);
	break;

     default:
	putnext(q, mp);
	return;
    }
vjc_ioc_ret:
    if (mp->b_cont) {
	freemsg(mp->b_cont);
	mp->b_cont = NULL;
    }
    ioc->ioc_count = 0;
vjc_ioc_data_ret:
    if (err) {
	ioc->ioc_error = err;
	mp->b_datap->db_type = M_IOCNAK;
    }
    else
	mp->b_datap->db_type = M_IOCACK;
    qreply(q, mp);
}

#define	STATE_SIZE(n)	((size_t)((n) * sizeof(cstate_t)))

vjc_tcpenable(vjc, nstates)
vjc_t *vjc;
unsigned nstates;
{
    int ostates;
#ifdef	SOLARIS1
    int s = splstr();
#endif
    mutex_enter(&vjc->tlock);
    mutex_enter(&vjc->rlock);
    if  (nstates == 1)
	nstates = VJC_DEF_STATES;
    if (vjc->nstates == nstates)
	goto vjc_tcpenable_done;
    if (vjc->nstates) {
	ostates = vjc->nstates;
	vjc->nstates = 0;
	vjc->flags &= ~VJCF_COMPTCP;
	kmem_free((caddr_t)vjc->rstate, STATE_SIZE(ostates));
	kmem_free((caddr_t)vjc->tstate, STATE_SIZE(ostates));
	vjc->tstate = vjc->rstate = (cstate_t *)0;
    }
    if (nstates) {
	vjc->tstate = (cstate_t *)kmem_zalloc(STATE_SIZE(nstates), KM_NOSLEEP);
	vjc->rstate = (cstate_t *)kmem_zalloc(STATE_SIZE(nstates), KM_NOSLEEP);
	if (!vjc->tstate || !vjc->tstate) {
	    if (vjc->tstate)
		kmem_free((caddr_t)vjc->tstate, STATE_SIZE(nstates));
	    if (vjc->rstate)
		kmem_free((caddr_t)vjc->rstate, STATE_SIZE(nstates));
	    return ENOMEM;
	}
	vjc->nstates = nstates;
	vjc_tinit(vjc);
	vjc_rinit(vjc);
	vjc->flags |= VJCF_COMPTCP;
    }
 vjc_tcpenable_done:
    DLOG1("vjc: %d states\n", vjc->nstates);
    mutex_exit(&vjc->rlock);
    mutex_exit(&vjc->tlock);
#ifdef	SOLARIS1
    splx(s);
#endif
    return 0;
}

static void
vjc_tinit(vjc)
vjc_t *vjc;
{
    register u_int i;
    register cstate_t *tstate = vjc->tstate;

    if (!vjc->nstates)
	return;

    bzero((char *)vjc->tstate, STATE_SIZE(vjc->nstates));
    for (i = vjc->nstates - 1 ; i ; --i) {
	tstate[i].cs_id = i;
	tstate[i].cs_next = &tstate[i - 1];
    }
    tstate[0].cs_next = &tstate[vjc->nstates - 1];
    tstate[0].cs_id = 0;
    vjc->last_cs = &tstate[0];
    vjc->last_xmit = VJC_MAX_STATES + 1;
}

static void
vjc_rinit(vjc)
vjc_t *vjc;
{
    if (!vjc->nstates)
	return;

    bzero((char *)vjc->rstate, STATE_SIZE(vjc->nstates));
    vjc->last_recv = VJC_MAX_STATES + 1;
    vjc->flags |= VJCF_TOSS;
}

/*
 * This function insures that the PPP Header is contained in the
 * first message block and the IP and TCP headers are both
 * contained in the next message block, perhaps with some additional
 * data.
 */

vjc_pullup_tcp(pmp, mlen)
mblk_t *pmp;
int mlen;
{
    u_int hlen;
    struct ppp_header *ph;
    struct ip *ip;
    struct tcphdr *th;
    mblk_t *imp, *rmp;
    u_short proto;

    if (mlen < PPP_HDR_SIZE)
	return VPT_BAD_FMT;
    if (!MIN_MSG_DATA(pmp, PPP_HDR_SIZE))
	return VPT_NO_MEM;
    /*
     * Examine the PPP Header
     */
    ph = (struct ppp_header *)pmp->b_rptr;
    switch (proto = ntohs(ph->ph_protocol)) {
     case PPP_IP:
     case PPP_VJC_UNCOMP:
     case PPP_VJC_COMP:
	break;
     default:
	return VPT_NOT_TCP;
    }

    /*
     * If more than the PPP header is in the first message block, 
     * take a little time to split it up to make further processing simpler.
     */
    if (pmp->b_wptr - pmp->b_rptr > PPP_HDR_SIZE) {
	imp = pmp;
	pmp = dupb(imp);
	pmp->b_wptr = pmp->b_rptr + PPP_HDR_SIZE;
	imp->b_rptr += PPP_HDR_SIZE;
	pmp->b_cont = imp;
    }

    /*
     * Now the first message block contains just the PPP header.
     */
    imp = pmp->b_cont;
    mlen -= PPP_HDR_SIZE;
    ip = (struct ip *)imp->b_rptr;

    if (proto == PPP_VJC_COMP) {
	if (pullupmsg(imp, (mlen < VJC_MAX_COMP) ? mlen : VJC_MAX_COMP))
	    return VPT_SUCCESS;
	else
	    return VPT_NO_MEM;
    }
    if (mlen < sizeof(struct ip))
	return VPT_BAD_FMT;
    if (!MIN_MSG_DATA(imp, VJC_MIN_IPTCP))
	return VPT_NO_MEM;
    /*
     * Examine the IP Header
     */
    if (mlen < VJC_MIN_IPTCP)
	return VPT_NOT_TCP;
    if (proto == PPP_IP && ip->ip_p != IPPROTO_TCP)
	return VPT_NOT_TCP;
    /*
     * Make sure that the whole IP + TCP Header is in the second
     * message block
     */
    hlen = (ip->ip_hl<<2);
    if (mlen < hlen + sizeof(struct tcphdr))
	return VPT_BAD_FMT;
    if (!MIN_MSG_DATA(imp, hlen + sizeof(struct tcphdr)))
	return VPT_NO_MEM;
    th = (struct tcphdr *)(imp->b_rptr + hlen);
    hlen += th->th_off << 2;
    if (mlen < hlen)
	return VPT_BAD_FMT;
    if (!MIN_MSG_DATA(imp, hlen))
	return VPT_NO_MEM;

    return VPT_SUCCESS;
}

/* ENCODE encodes a number that is known to be non-zero.  ENCODEZ
 * checks for zero (since zero has to be encoded in the long, 3 byte
 * form).
 */
#define ENCODE(n) { \
	if ((u_short)(n) >= 256) { \
		*cp++ = 0; \
		cp[1] = (n); \
		cp[0] = (n) >> 8; \
		cp += 2; \
	} else { \
		*cp++ = (n); \
	} \
}
#define ENCODEZ(n) { \
	if ((u_short)(n) >= 256 || (u_short)(n) == 0) { \
		*cp++ = 0; \
		cp[1] = (n); \
		cp[0] = (n) >> 8; \
		cp += 2; \
	} else { \
		*cp++ = (n); \
	} \
}

#define DECODEL(f) { \
	if (*cp == 0) {\
		(f) = htonl(ntohl(f) + ((cp[1] << 8) | cp[2])); \
		cp += 3; \
	} else { \
		(f) = htonl(ntohl(f) + (u_long)*cp++); \
	} \
}

#define DECODES(f) { \
	if (*cp == 0) {\
		(f) = htons(ntohs(f) + ((cp[1] << 8) | cp[2])); \
		cp += 3; \
	} else { \
		(f) = htons(ntohs(f) + (u_long)*cp++); \
	} \
}

#define DECODEU(f) { \
	if (*cp == 0) {\
		(f) = htons((cp[1] << 8) | cp[2]); \
		cp += 3; \
	} else { \
		(f) = htons((u_long)*cp++); \
	} \
}

/*
 * This function implements Van Jacobson TCP compression.
 * Some higher level modules may have dup'ed this message block,
 * so if we need to change packet contents, we don't change the
 * actual packet.  We do, however, allow changes to the PPP header,
 * since that was generated in a module that is part of this package
 * and is never dup'ed.
 */

static mblk_t *
compress_tcp(pmp, vjc, compress_cid)
mblk_t *pmp;
vjc_t *vjc;
int compress_cid;
{
    mblk_t *imp = pmp->b_cont, *nimp, *rmp;
    register struct ip *ip = (struct ip *)imp->b_rptr;
    register cstate_t *cs;
    register u_int hlen = ip->ip_hl;
    register struct tcphdr *oth;
    register struct tcphdr *th;
    register u_int deltaS, deltaA;
    register u_int changes = 0;
    u_char new_seq[16];
    register u_char *cp = new_seq;

    /*
     * Bail if this is an IP fragment or if the TCP packet isn't
     * `compressible' (i.e., ACK isn't set or some other control bit is
     * set).  (We assume that the caller has already made sure the
     * packet is IP proto TCP).
     */
    if ((ip->ip_off & htons(0x3fff)) || (imp->b_wptr - imp->b_rptr) < 40)
	return pmp;

    th = (struct tcphdr *)&((int *)ip)[hlen];
    if ((th->th_flags & (TH_SYN|TH_FIN|TH_RST|TH_ACK)) != TH_ACK)
	return pmp;
    /*
     * Packet is compressible -- we're going to send either a
     * PPP_VJC_COMP or PPP_VJC_UNCOMP packet.  Either way we need
     * to locate (or create) the connection state.  Special case the
     * most recently used connection since it's most likely to be used
     * again & we don't have to do any reordering if it's used.
     */
    mutex_enter(&vjc->tlock);
    VJCINCR(packets)
    cs = vjc->last_cs->cs_next;
    if (ip->ip_src.s_addr != cs->cs_ip.ip_src.s_addr ||
	ip->ip_dst.s_addr != cs->cs_ip.ip_dst.s_addr ||
	*(int *)th != ((int *)&cs->cs_ip)[cs->cs_ip.ip_hl]) {
	/*
	 * Wasn't the first -- search for it.
	 *
	 * States are kept in a circularly linked list with
	 * last_cs pointing to the end of the list.  The
	 * list is kept in lru order by moving a state to the
	 * head of the list whenever it is referenced.  Since
	 * the list is short and, empirically, the connection
	 * we want is almost always near the front, we locate
	 * states via linear search.  If we don't find a state
	 * for the datagram, the oldest state is (re-)used.
	 */
	register cstate_t *lcs;
	register cstate_t *lastcs = vjc->last_cs;

	do {
	    lcs = cs; cs = cs->cs_next;
	    VJCINCR(searches)
	    if (ip->ip_src.s_addr == cs->cs_ip.ip_src.s_addr
		&& ip->ip_dst.s_addr == cs->cs_ip.ip_dst.s_addr
		&& *(int *)th == ((int *)&cs->cs_ip)[cs->cs_ip.ip_hl])
		goto found;
	} while (cs != lastcs);

	/*
	 * Didn't find it -- re-use oldest cstate.  Send an
	 * uncompressed packet that tells the other side what
	 * connection number we're using for this conversation.
	 * Note that since the state list is circular, the oldest
	 * state points to the newest and we only need to set
	 * last_cs to update the lru linkage.
	 */
	VJCINCR(misses)
	vjc->last_cs = lcs;
	hlen += th->th_off;
	hlen <<= 2;
	goto uncompressed;

found:
	/*
	 * Found it -- move to the front on the connection list.
	 */
	if (cs == lastcs)
	    vjc->last_cs = lcs;
	else {
	    lcs->cs_next = cs->cs_next;
	    cs->cs_next = lastcs->cs_next;
	    lastcs->cs_next = cs;
	}
    }

    /*
     * Make sure that only what we expect to change changed. The first
     * line of the `if' checks the IP protocol version, header length &
     * type of service.  The 2nd line checks the "Don't fragment" bit.
     * The 3rd line checks the time-to-live and protocol (the protocol
     * check is unnecessary but costless).  The 4th line checks the TCP
     * header length.  The 5th line checks IP options, if any.  The 6th
     * line checks TCP options, if any.  If any of these things are
     * different between the previous & current datagram, we send the
     * current datagram `uncompressed'.
     */
    oth = (struct tcphdr *)&((int *)&cs->cs_ip)[hlen];
    deltaS = hlen;
    hlen += th->th_off;
    hlen <<= 2;

    if (((u_short *)ip)[0] != ((u_short *)&cs->cs_ip)[0] ||
	((u_short *)ip)[3] != ((u_short *)&cs->cs_ip)[3] ||
	((u_short *)ip)[4] != ((u_short *)&cs->cs_ip)[4] ||
	th->th_off != oth->th_off ||
	(deltaS > 5 &&
	 VJCBCMP(ip + 1, &cs->cs_ip + 1, (deltaS - 5) << 2)) ||
	(th->th_off > 5 &&
	 VJCBCMP(th + 1, oth + 1, (th->th_off - 5) << 2)))
	goto uncompressed;

    /*
     * Figure out which of the changing fields changed.  The
     * receiver expects changes in the order: urgent, window,
     * ack, seq (the order minimizes the number of temporaries
     * needed in this section of code).
     */
    if (th->th_flags & TH_URG) {
	deltaS = ntohs(th->th_urp);
	ENCODEZ(deltaS);
	changes |= NEW_U;
    } else if (th->th_urp != oth->th_urp)
	/* argh! URG not set but urp changed -- a sensible
	 * implementation should never do this but RFC793
	 * doesn't prohibit the change so we have to deal
	 * with it. */
	 goto uncompressed;

    if (deltaS = (u_short)(ntohs(th->th_win) - ntohs(oth->th_win))) {
	ENCODE(deltaS);
	changes |= NEW_W;
    }

    if (deltaA = ntohl(th->th_ack) - ntohl(oth->th_ack)) {
	if (deltaA > 0xffff)
	    goto uncompressed;
	ENCODE(deltaA);
	changes |= NEW_A;
    }

    if (deltaS = ntohl(th->th_seq) - ntohl(oth->th_seq)) {
	if (deltaS > 0xffff)
	    goto uncompressed;
	ENCODE(deltaS);
	changes |= NEW_S;
    }

    switch(changes) {

     case 0:
	/*
	 * Nothing changed. If this packet contains data and the
	 * last one didn't, this is probably a data packet following
	 * an ack (normal on an interactive connection) and we send
	 * it compressed.  Otherwise it's probably a retransmit,
	 * retransmitted ack or window probe.  Send it uncompressed
	 * in case the other side missed the compressed version.
	 */
	if (ip->ip_len != cs->cs_ip.ip_len &&
	    ntohs(cs->cs_ip.ip_len) == hlen)
	    break;

	/* (fall through) */

     case SPECIAL_I:
     case SPECIAL_D:
	/*
	 * actual changes match one of our special case encodings --
	 * send packet uncompressed.
	 */
	goto uncompressed;

     case NEW_S|NEW_A:
	if (deltaS == deltaA &&
	    deltaS == ntohs(cs->cs_ip.ip_len) - hlen) {
	    /* special case for echoed terminal traffic */
	    changes = SPECIAL_I;
	    cp = new_seq;
	}
	break;

     case NEW_S:
	if (deltaS == ntohs(cs->cs_ip.ip_len) - hlen) {
	    /* special case for data xfer */
	    changes = SPECIAL_D;
	    cp = new_seq;
	}
	break;
    }

    deltaS = ntohs(ip->ip_id) - ntohs(cs->cs_ip.ip_id);
    if (deltaS != 1) {
	ENCODEZ(deltaS);
	changes |= NEW_I;
    }
    if (th->th_flags & TH_PUSH)
	changes |= TCP_PUSH_BIT;
    /*
     * Grab the cksum before we overwrite it below.  Then update our
     * state with this packet's header.
     */
    deltaA = ntohs(th->th_sum);
    VJCBCOPY(ip, &cs->cs_ip, hlen);

    /*
     * We want to insert the compressed information in the list of mblk's
     * I am assuming that it is okay to modify the mblks, but not the
     * data blocks.  You could certainly copy the mblks before modification
     * if some higher level protocol is keeping copies of packets for
     * some other purpose.
     */
    deltaS = cp - new_seq;
    if (!(nimp = allocb(deltaS + 4, BPRI_MED)))
	return pmp;

    /*
     * Remove old IP/TCP header from packet, keeping track of the rest.
     */
    imp->b_rptr += hlen;
    if (imp->b_rptr == imp->b_wptr) {
	rmp = imp->b_cont;
	freeb(imp);
    }
    else
	rmp = imp;

    /*
     * Assemble the PPP header, the VJC Compressed IP/TCP header, and the rest.
     */
    pmp->b_cont = nimp;
    nimp->b_cont = rmp;


    /*
     * Fill in the compressed header, and update the PPP header.
     */
    cp = nimp->b_wptr;
    if (compress_cid == 0 || vjc->last_xmit != cs->cs_id) {
	vjc->last_xmit = cs->cs_id;
	*cp++ = changes | NEW_C;
	*cp++ = cs->cs_id;
    }
    else
	*cp++ = changes;
    *cp++ = deltaA >> 8;
    *cp++ = deltaA;
    VJCBCOPY(new_seq, cp, deltaS);
    nimp->b_wptr = cp + deltaS;

    ((struct ppp_header *)pmp->b_rptr)->ph_protocol = PPP_VJC_COMP;

    VJCINCR(compressed)
    mutex_exit(&vjc->tlock);
    return pmp;

    /*
     * Update connection state cs & send uncompressed packet ('uncompressed'
     * means a regular ip/tcp packet but with the 'conversation id' we hope
     * to use on future compressed packets in the protocol field).
     */
uncompressed:
    
    if (!(nimp = allocb(hlen, BPRI_MED)))
	return pmp;

    /*
     * Copy packet into our state table.
     */
    VJCBCOPY(ip, &cs->cs_ip, hlen);

    /*
     * Copy IP/TCP header into new header.
     */
    VJCBCOPY(ip, nimp->b_wptr, hlen);
    nimp->b_wptr += hlen;

    /*
     * Remove old IP/TCP header from packet, keeping track of the rest.
     */
    imp->b_rptr += hlen;
    if (imp->b_rptr == imp->b_wptr) {
	rmp = imp->b_cont;
	freeb(imp);
    }
    else
	rmp = imp;

    /*
     * Assemble the PPP header, the IP/TCP header, and the rest.
     */
    pmp->b_cont = nimp;
    nimp->b_cont = rmp;

    /*
     * Update the appropriate fields in the PPP and IP headers.
     */
    ((struct ip *)nimp->b_rptr)->ip_p = cs->cs_id;
    vjc->last_xmit = cs->cs_id;
    ((struct ppp_header *)pmp->b_rptr)->ph_protocol = PPP_VJC_UNCOMP;

    mutex_exit(&vjc->tlock);
    return pmp;
}

/*
 * uncompress_tcp() implements the Van Jacobson TCP Uncompression.
 *
 *  pmp		A message block containing the PPP header.  The
 *		second message block is guaranteed to conatain at
 *		least the IP & TCP headers.
 *  len		the total length of the packet, including IP & TCP headers.
 *  type	either PPP_COMPRESSED_TCP or PPP_UNCOMPTRESSED_TCP
 *  vjc		Compression State
 *
 *  Note that it is okay to modify the data in place since we are
 *  reasonably sure that the link counts in the data buffers should
 *  be 1.  If this turns out to be untrue, this code will need to be
 *  modified to copy data before modifying it.
 */

mblk_t *
uncompress_tcp(pmp, vjc, len)
mblk_t *pmp;
vjc_t *vjc;
int len;
{
    register u_char *cp;
    register u_int hlen, changes;
    register struct tcphdr *th;
    register cstate_t *cs;
    register struct ip *ip;
    mblk_t *cmp, *imp;
    struct ppp_header *ph = (struct ppp_header *)pmp->b_rptr;
#ifdef	SOLARIS1
    int s = splstr();
#endif
    mutex_enter(&vjc->rlock);

    switch (ntohs(ph->ph_protocol)) {
     case PPP_VJC_UNCOMP:
	ip = (struct ip *)pmp->b_cont->b_rptr;
	if (ip->ip_p >= vjc->nstates)
		goto bad;
	cs = &vjc->rstate[vjc->last_recv = ip->ip_p];
	vjc->flags &=~ VJCF_TOSS;
	ip->ip_p = IPPROTO_TCP;
	hlen = ip->ip_hl;
	hlen += ((struct tcphdr *)&((int *)ip)[hlen])->th_off;
	hlen <<= 2;
	VJCBCOPY(ip, &cs->cs_ip, hlen);
	cs->cs_ip.ip_sum = 0;
	cs->cs_hlen = hlen;
	ph->ph_protocol = htons(PPP_IP);
	VJCINCR(uncompressedin)
#ifdef	SOLARIS1
	splx(s);
#endif
	mutex_exit(&vjc->rlock);
	return pmp;

     default:
	goto bad;

     case PPP_VJC_COMP:
	break;
    }
    /* We've got a compressed packet. */
    VJCINCR(compressedin)
    cmp = pmp->b_cont;
    cp = cmp->b_rptr;
    changes = *cp++;
    if (changes & NEW_C) {
	/* Make sure the state index is in range, then grab the state.
	 * If we have a good state index, clear the 'discard' flag. */
	if (*cp >= vjc->nstates)
	    goto bad;

	vjc->flags &=~ VJCF_TOSS;
	vjc->last_recv = *cp++;
    } else {
	/* this packet has an implicit state index.  If we've
	 * had a line error since the last time we got an
	 * explicit state index, we have to toss the packet. */
	if (vjc->flags & VJCF_TOSS) {
	    VJCINCR(tossed)
	    freemsg(pmp);
	    mutex_exit(&vjc->rlock);
	    return (mblk_t *)0;
	}
    }
    cs = &vjc->rstate[vjc->last_recv];
    hlen = cs->cs_ip.ip_hl << 2;
    th = (struct tcphdr *)&((u_char *)&cs->cs_ip)[hlen];
    th->th_sum = htons((*cp << 8) | cp[1]);
    cp += 2;
    if (changes & TCP_PUSH_BIT)
	th->th_flags |= TH_PUSH;
    else
	th->th_flags &=~ TH_PUSH;

    switch (changes & SPECIALS_MASK) {
     case SPECIAL_I:
	{
	    register u_int i = ntohs(cs->cs_ip.ip_len) - cs->cs_hlen;
	    th->th_ack = htonl(ntohl(th->th_ack) + i);
	    th->th_seq = htonl(ntohl(th->th_seq) + i);
	}
	break;

     case SPECIAL_D:
	th->th_seq = htonl(ntohl(th->th_seq) + ntohs(cs->cs_ip.ip_len)
			   - cs->cs_hlen);
	break;

     default:
	if (changes & NEW_U) {
	    th->th_flags |= TH_URG;
	    DECODEU(th->th_urp)
	} else
	    th->th_flags &=~ TH_URG;
	if (changes & NEW_W)
	    DECODES(th->th_win)
	if (changes & NEW_A)
	    DECODEL(th->th_ack)
	if (changes & NEW_S)
	    DECODEL(th->th_seq)
	break;
    }
    if (changes & NEW_I) {
	DECODES(cs->cs_ip.ip_id)
    } else
	cs->cs_ip.ip_id = htons(ntohs(cs->cs_ip.ip_id) + 1);

    /*
     * If we have used more data than we have, then we must have dropped
     * some characters (crc should detect this but the old slip framing
     * won't)
     */
    if (cp > cmp->b_wptr)
	goto bad;
    
    /*
     * Figure out our new packet length
     */
    len = len - (cp - cmp->b_rptr) + cs->cs_hlen - PPP_HDR_SIZE;
    cs->cs_ip.ip_len = htons(len);

    /*
     * Adjust the associated message blocks to indicate that we have
     * real IP & TCP headers, but no compressed information.
     */
    cmp->b_rptr = cp;

    if (!(imp = allocb(cs->cs_hlen, BPRI_MED)))
	goto bad2;
    VJCBCOPY(&cs->cs_ip, imp->b_wptr, cs->cs_hlen);
    imp->b_wptr += cs->cs_hlen;

    pmp->b_cont = imp;
    imp->b_cont = cmp;
    /*
     * Recompute the IP Header Checksum
     */
    {
	register u_short *bp = (u_short *)imp->b_rptr;
	for (changes = 0; hlen != 0; hlen -= 2)
	    changes += *bp++;
	changes = (changes & 0xffff) + (changes >> 16);
	changes = (changes & 0xffff) + (changes >> 16);
	((struct ip *)imp->b_rptr)->ip_sum = ~ changes;
    }
    ph->ph_protocol = htons(PPP_IP);
#ifdef	SOLARIS1
    splx(s);
#endif
    mutex_exit(&vjc->rlock);
    return pmp;

   bad:
    vjc->flags |= VJCF_TOSS;
    VJCINCR(errorin)
   bad2:
    freemsg(pmp);
#ifdef	SOLARIS1
    splx(s);
#endif
    mutex_exit(&vjc->rlock);
    return (mblk_t *)0;
}

#ifdef	SOLARIS1
#include <sys/conf.h>
#include <sys/buf.h>
#include <sundev/mbvar.h>
#include <sun/autoconf.h>
#include <sun/vddrv.h>

static struct vdldrv vjc_vd = {
    VDMAGIC_PSEUDO,
    "Van Jacobson TCP Compression",
};
static struct fmodsw *fmod_vjc;

xxxinit(fc,vdp,vdi,vds)
unsigned int fc;
struct vddrv *vdp;
addr_t vdi;
struct vdstat *vds;
{
    switch (fc) {
     case VDLOAD:
	{
	    int dev, i;
	    for (dev = 0; dev < fmodcnt; dev++) {
		if(fmodsw[dev].f_str == NULL)
		    break;
	    }
	    if (dev == fmodcnt)
		return ENODEV;
	    fmod_vjc = &fmodsw[dev];
	    for(i = 0; i <= FMNAMESZ; i++)
		fmod_vjc->f_name[i] = vjc_info.mi_idname[i];
	    fmod_vjc->f_str = &vjcinfo;
	    vdp->vdd_vdtab = (struct vdlinkage *) &vjc_vd;
	}
	return 0;
     case VDUNLOAD:
	if (vjcs)
	    return EBUSY;

	fmod_vjc->f_name[0] = '\0';
	fmod_vjc->f_str = NULL;
	return 0;
     case VDSTAT:
	return 0;
     default:
	return EIO;
    }
}
#else

/*
 * Solaris 2.X version
 */
#include <sys/modctl.h>

extern struct streamtab vjcinfo;

static struct fmodsw vjcfsw = {
    "vjc",
    &vjcinfo,
    D_NEW | D_MP
};
	
extern struct mod_ops mod_strmodops;

static struct modlstrmod vjclstrmod = {
    &mod_strmodops,
    "Van Jacobson Compression Module v3.0",
    &vjcfsw
};

static struct modlinkage vjcmodlinkage = {
    MODREV_1,
    (void *)&vjclstrmod,
    NULL
};
/*
 * Module Config Entry Points.
 */

_init()
{
    mutex_init(&vjcs_lock, "vjc module list lock",
	       MUTEX_DRIVER, (void *)0);
    return (mod_install(&vjcmodlinkage));
}

_fini()
{
    register int ret;

    if ((ret = mod_remove(&vjcmodlinkage)) == DDI_SUCCESS) {
	mutex_destroy(&vjcs_lock);
	return ret;
    }
    else
	return EBUSY;
}

_info(modinfop)
struct modinfo *modinfop;
{
    return mod_info(&vjcmodlinkage, modinfop);
}

#endif
