/*
 *			COMPRESSED TCP HEADERS
 *
 * Reduce the 40 byte TCP header to as little as 5 or even 3 bytes
 * (of which 2 bytes are the original TCP checksum).
 *
 * See RFC 1144 "Compression of TCP/IP Headers for Low-Speed Serial
 * Links", V. Jacobson, February 1990, for the exciting details.
 *
 * The code in this file is derived from and very similar to the example
 * implementation provided in RFC 1144, and is therefore sort-of covered
 * by its copyright:
 *
 * Copyright (C) 1989 Regents of the University of California.
 *
 * This adaptation by Olaf 'Rhialto' Seibert.
 * - fixed a number of sizeof(int) == 4 assumptions.
 * - allow a few packets to be sent compressed before it is officially
 *   enabled, so as to trigger the other side into compressed mode.
 *
 * Memory usage: < 3K of 68000 code, plus ~ 4K buffer space per line.
 */

#include <string.h>
#include "slip_device.h"
#if DEBUG
#include "syslog.h"
#else
#define debug(x)
#endif

#define calloc(n,m)     AllocVec((n) * (m), MEMF_CLEAR)
#define free(p)         FreeVec(p)

#ifndef EXEC_MEMORY_H
#include <exec/memory.h>
#endif
#include <clib/exec_protos.h>
#ifdef __SASC
#include <pragmas/exec_pragmas.h>
#endif

#define ihl(v_ihl)      lonibble(v_ihl) /* extract ip header length */
#define tcp_offset(tcp)  ((u_char)(tcp)->offset >> DSHIFT)

/* Appendix A.2  Compression */

u_char
sl_compress_tcp(m, comp)
struct mbuf *m;
struct slcompress *comp;
{
    register struct cstate *cs = comp->last_cs->cs_next;
    register struct ip_header *ip = mtod(m, struct ip_header *);
    register u_int hlen = ihl(ip->v_ihl);
    register struct tcp_header *oth;	/* last TCP header */
    register struct tcp_header *th;	/* current TCP header */
    register u_long deltaS, deltaA;	/* general purpose temporaries */
    register u_int changes = 0; 	/* change mask */
    u_char new_seq[16]; 		/* changes from last to current */
    register u_char *cp = new_seq;

    debug((">sl_compress_tcp %lx %d\n", m->m_off, m->m_len));
    /* Appendix B.2  Backwards compatible SLIP servers */
#ifdef TRYCOUNT
    if (comp->on == 0) {
	debug(("<sl_compress_tcp: not on\n"));
	return TYPE_IP;
    }
#else
    if ((comp->flags & SLF_ON) == 0) {
	debug(("<sl_compress_tcp: not on\n"));
	return TYPE_IP;
    }
#endif

    if (ip->protocol != TCP_PTCL) {     /* Rhialto */
	comp->sls_o_nontcp++;
	debug(("<sl_compress_tcp: not tcp\n"));
	return TYPE_IP;
    }

    /*
     * 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->fl_offs & htons(0x3fff)) || m->m_len < 40) {
	comp->sls_o_tcp++;
	debug(("<sl_compress_tcp: tcp frag\n"));
	return TYPE_IP;
    }

    th = (struct tcp_header *)&((int32 *) ip)[hlen];
    if ((th->flags & (SYN | FIN | RST | ACK)) != ACK) {
	comp->sls_o_tcp++;
	debug(("<sl_compress_tcp: tcp flags\n"));
	return TYPE_IP;
    }

    /*
     * Packet is compressible -- we're going to send either a
     * COMPRESSED_TCP or UNCOMPRESSED_TCP 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.
     */
    if (ip->source != cs->cs_ip.source ||
	ip->dest != cs->cs_ip.dest ||
	*(int32 *)th != cs->cs_hdr4[ihl(cs->cs_ip.v_ihl)]) {

	/*
	 * 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 struct cstate *lcs;
	register struct cstate *lastcs = comp->last_cs;

	do {
	    lcs = cs;
	    cs = cs->cs_next;
	    comp->sls_o_searches++;

	    if (ip->source == cs->cs_ip.source &&
		ip->dest == cs->cs_ip.dest &&
		*(int32 *)th == cs->cs_hdr4[ihl(cs->cs_ip.v_ihl)])
		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.
	 */
	comp->sls_o_misses++;
	comp->last_cs = lcs;
	hlen += tcp_offset(th); /* add tcp header length */
	hlen *= sizeof(int32);

	goto uncompressed;

    found:
	/* Found it -- move to the front on the connection list. */
	if (lastcs == cs)
	    comp->last_cs = lcs;
	else {
	    lcs->cs_next = cs->cs_next;
	    cs->cs_next = lastcs->cs_next;
	    lastcs->cs_next = cs;
	}
    }
    debug((" sl_compress_tcp: found\n"));
#ifdef TRYCOUNT
    /*
     * For found connections, try a few TCP_UNCOMPRESSED packets even if
     * we're not SL_ON yet. (This doesn't happen on new connections;
     * they will be TCP_UNCOMPRESSED regardless - FIXME.)
     */
    if (comp->on > 0) {
	comp->on--;
	debug((" sl_compress_tcp: trycount %d\n", comp->on));
	goto uncompressed;
    }
#endif
    /*
     * Make sure that only what we expect to change changed. The 1st
     * 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 tcp_header *) &cs->cs_hdr4[hlen];
    deltaS = hlen;
    hlen += tcp_offset(th);
    hlen *= sizeof(int32);

    if (((u_short *) ip)[0] != ((u_short *) &cs->cs_ip)[0] || /* v_ihl,tos */
	((u_short *) ip)[3] != ((u_short *) &cs->cs_ip)[3] || /* fl_offs */
	((u_short *) ip)[4] != ((u_short *) &cs->cs_ip)[4] || /* ttl, protocol */
	th->offset != oth->offset ||
	(deltaS > 5 && BCMP(ip + 1, &cs->cs_ip + 1, (deltaS - 5) * sizeof(int32))) ||
	(tcp_offset(th) > 5 && BCMP(th + 1, oth + 1, (tcp_offset(th) - 5) * sizeof(int32)))) {
	debug((" sl_compress_tcp: header changed\n"));
	goto uncompressed;
    }

    /*
     * Figure out which of the changing fields changed. The receiver
     * expects changes in the order: urgent, window, ack, seq.
     */
    if (th->flags & URG) {
	deltaS = ntohs(th->up);
	ENCODEZ(deltaS);
	changes |= NEW_U;
    } else if (th->up != oth->up) {
	/*
	 * argh! URG not set but urp changed -- a sensible
	 * implementation should never do this but RFC 793 doesn't
	 * prohibit the change so we have to deal with it.
	 */
	debug((" sl_compress_tcp: up changed\n"));
	goto uncompressed;
    }

    if (deltaS = (u_short) (ntohs(th->wnd) - ntohs(oth->wnd))) {
	ENCODE(deltaS);
	changes |= NEW_W;
    }
    if (deltaA = ntohs(th->ack) - ntohs(oth->ack)) {
	if (deltaA > 0xffff)
	    goto uncompressed;
	ENCODE(deltaA);
	changes |= NEW_A;
    }
    if (deltaS = ntohs(th->seq) - ntohs(oth->seq)) {
	if (deltaS > 0xffff)
	    goto uncompressed;
	ENCODE(deltaS);
	changes |= NEW_S;
    }
    /*
     * Look for the special-case encodings.
     */
    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->length != cs->cs_ip.length &&
	    ntohs(cs->cs_ip.length) == hlen)
	    break;

	debug((" sl_compress_tcp: retransmission ->\n"));
	/* fall through */

    case SPECIAL_I:
    case SPECIAL_D:
	/*
	 * Actual changes match one of our special case encodings --
	 * send the packet uncompressed.
	 */
	debug((" sl_compress_tcp: changes match special\n"));
	goto uncompressed;

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

    case NEW_S:
	if (deltaS == ntohs(cs->cs_ip.length) - hlen) {
	    /* special case for data xfer */
	    debug((" sl_compress_tcp: special data\n"));
	    changes = SPECIAL_D;
	    cp = new_seq;
	}
	break;
    }
    deltaS = ntohs(ip->id) - ntohs(cs->cs_ip.id);
    if (deltaS != 1) {
	ENCODEZ(deltaS);
	changes |= NEW_I;
    }
    if (th->flags & PSH)
	changes |= TCP_PUSH_BIT;
    /*
     * Grab the cksum before we overwrite it below. Then update our
     * state with this packet's header.
     */
    deltaA = ntohs(th->checksum);
    BCOPY(ip, &cs->cs_ip, hlen);

    /*
     * We want to use the original packet as our compressed packet.
     * (cp - new_seq) is the number of bytes we need for compressed
     * sequence numbers. In addition we need 1 byte for the change
     * mask, 1 for the connection id and 2 for the tcp checksum.
     * So, (cp - new_seq) + 4 bytes of header are needed.
     * hlen is how many bytes of the original packet we tossed so
     * subtract the two to to get the packet size difference.
     */

    deltaS = cp - new_seq;
    cp = (u_char *)ip;
    if (comp->last_xmit != cs->cs_id) {
	comp->last_xmit = cs->cs_id;
	hlen -= deltaS + 4;
	cp += hlen;
	*cp++ = changes | NEW_C;
	*cp++ = cs->cs_id;
    } else {
	hlen -= deltaS + 3;
	cp += hlen;
	*cp++ = changes;
    }
    m->m_len -= hlen;
    m->m_off += hlen;
    *cp++ = deltaA >> 8;	/* checksum */
    *cp++ = deltaA;
    BCOPY(new_seq, cp, deltaS);

    comp->sls_o_compressed++;
    debug(("<sl_compress_tcp: compressed\n"));
    return TYPE_COMPRESSED_TCP;

uncompressed:
    debug((" sl_compress_tcp: uncompressed\n"));
    /*
     * 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).
     */
    BCOPY(m->m_off, cs->cs_hdr, hlen);
    ip->protocol = cs->cs_id;
    comp->last_xmit = cs->cs_id;

    comp->sls_o_uncompressed++;
    debug(("<sl_compress_tcp: uncompressed\n"));
    return TYPE_UNCOMPRESSED_TCP;
}

/* OIS:
 * This function should be called if the most recently compressed packet
 * may have been lost on its way to the next gateway. It forces the next
 * packet on the same connection to be uncompressed, in an attempt to
 * minimise the effect of the dropped packet. The method used is to change
 * one of the 'unchanging' header fields in the saved header, so that none
 * of the other saved headers will be dropped.
 */
void
sl_xmit_error(comp)
struct slcompress *comp;
{
    if (comp->last_xmit < MAX_STATES) {
	comp->tstate[comp->last_xmit].cs_ip.v_ihl--;
    }
}

/* Appendix A.3  Decompression */

struct mbuf *
sl_uncompress_tcp(m, type, comp)
struct mbuf *m;
u_int type;
struct slcompress *comp;
{
    register char *bufp;
    register u_char *cp;
    register u_int hlen, changes;
    register struct tcp_header *th;
    register struct cstate *cs;
    register struct ip_header *ip;
    int len;

    debug((">sl_uncompress_tcp\n"));

    switch (type) {
    case TYPE_ERROR:
    default:
	debug((" sl_uncompress_tcp: ERROR\n"));
	goto bad;
    case TYPE_IP:
	debug((" sl_uncompress_tcp: IP\n"));
	comp->sls_i_ip++;
#ifdef SLF_MAYBE
	/*
	 * If we receive TYPE_IP packets, investigate if they could have
	 * been sent compressed. If so, the other side probably doesn't
	 * understand compressed packets. This test is identical to those
	 * in sl_compress_tcp().
	 * (OIS)
	 */
	bufp = m->m_off;
	ip = (struct ip_header *)bufp;
	if (comp->flags & SLF_MAYBE &&
	    ip->protocol == TCP_PTCL &&
	    (ip->fl_offs & htons(0x3fff)) == 0 &&
	    (th = (struct tcp_header *)
		 offset(m, ihl(ip->v_ihl) * sizeof(int32), sizeof(*th))) &&
	    (th->flags & (SYN | FIN | RST | ACK)) == ACK) {
	    comp->flags &= ~(SLF_ON | SLF_MAYBE) ;
	    /* printf("Compression turned OFF\n"); */
	}
#endif
	return m;
    case TYPE_UNCOMPRESSED_TCP:
	debug((" sl_uncompress_tcp: UNCOMPRESSED TCP\n"));
	comp->sls_i_uncompressed++;
	/* Appendix B.2  Backwards compatible SLIP servers */
	if ((comp->flags & SLF_ALLOWED) == 0)
	    goto bad;
#ifdef TRYCOUNT
	comp->on = SL_ON;
#else
	comp->flags |= SLF_ON;
#endif
	/*
	 * Locate the saved state for this connection. If the state index
	 * is legal, clear the 'discard' flag.
	 */
	bufp = m->m_off;
	ip = (struct ip_header *)bufp;
	if (ip->protocol > comp->rslot_limit)
	    goto bad;

	cs = &comp->rstate[comp->last_recv = ip->protocol];
	comp->flags &= ~SLF_TOSS;
	/*
	 * Restore the IP protocol field then save a copy of this
	 * packet header. (The checksum is zeroed in the copy so we
	 * don't have to zero it each time we process a compressed
	 * packet.)
	 */
	ip->protocol = TCP_PTCL;
	hlen = ihl(ip->v_ihl);
	hlen += tcp_offset((struct tcp_header *) & ((int32 *)ip)[hlen]);
	hlen *= sizeof(int32);
	BCOPY(ip, cs->cs_hdr, hlen);
	cs->cs_ip.checksum = 0;
	cs->cs_hlen = hlen;

	return m;

    case TYPE_COMPRESSED_TCP:
	debug((" sl_uncompress_tcp: COMPRESSED TCP\n"));
	comp->sls_i_compressed++;
	/* Appendix B.2  Backwards compatible SLIP servers */
	if ((comp->flags & SLF_ALLOWED) == 0)
	    goto bad;
	break;
    }

    /* We've got a compressed packet. */
    bufp = m->m_off;
    cp = bufp;
    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 > comp->rslot_limit)
	    goto bad;

	comp->flags &= ~SLF_TOSS;
	comp->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 (comp->flags & SLF_TOSS) {
	    comp->sls_i_tossed++;
	    debug(("<sl_uncompress_tcp: tossed\n"));
	    return NULL;
	}
    }

    /*
     * Find the state then fill in the TCP checksum and PUSH bit.
     */
    cs = &comp->rstate[comp->last_recv];
    hlen = ihl(cs->cs_ip.v_ihl) * sizeof(int32);
    th = (struct tcp_header *) &((u_char *) &cs->cs_ip)[hlen];
    th->checksum = htons((*cp << 8) | cp[1]);
    cp += 2;
    if (changes & TCP_PUSH_BIT)
	th->flags |= PSH;
    else
	th->flags &= ~PSH;

    /*
     * Fix up the state's ack, seq, urg and win fields based on the
     * changemask.
     */
    switch (changes & SPECIALS_MASK) {
    case SPECIAL_I:
	{
	    register u_int i = ntohs(cs->cs_ip.length) - cs->cs_hlen;
	    debug((" sl_uncompress_tcp: special interactive\n"));

	    th->ack = htonl(ntohl(th->ack) + i);
	    th->seq = htonl(ntohl(th->seq) + i);
	}
	break;
    case SPECIAL_D:
	    debug((" sl_uncompress_tcp: special data\n"));
	th->seq = htonl(ntohl(th->seq) + ntohs(cs->cs_ip.length)
			- cs->cs_hlen);
	break;
    default:
	debug((" sl_uncompress_tcp: other changemask\n"));
	if (changes & NEW_U) {
	    th->flags |= URG;
	    DECODEU(th->up);
	} else
	    th->flags &= ~URG;
	if (changes & NEW_W)
	    DECODES(th->wnd);
	if (changes & NEW_A)
	    DECODEL(th->ack);
	if (changes & NEW_S)
	    DECODEL(th->seq);
    }

    /* Update the new IP ID */
    if (changes & NEW_I)
	DECODES(cs->cs_ip.id)
    else
	cs->cs_ip.id = htons(ntohs(cs->cs_ip.id) + 1);

    /*
     * At this point we have done the whole header.
     * cp points to the first byte of data in the packet. We skip the
     * 4-byte alignment here as SANA will copy it to the user anyway.
     * Back up cp by the TCP/IP header length to make room for the
     * reconstructed header (we assume the packet we were handed has
     * enough space to prepend 128 bytes of header). Adjust the length
     * to account for the new header & fill in the IP total length.
     *
     */
    len = m->m_len;
    len -= (cp - bufp);
    if (len < 0)
	/*
	 * We must have dropped some characters (crc should detect this
	 * but the slip framing won't)
	 */
	goto bad;

#if 0
    if ((int32)cp & 3) {
	if (len > 0)
	    OVBCOPY(cp, (int32)cp & ~3, len);
	cp = (u_char *) ((int32)cp & ~3);
    }
#endif

    m->m_off = cp -= cs->cs_hlen;
    m->m_len = len += cs->cs_hlen;
    cs->cs_ip.length = htons(len);
    debug((" sl_uncompress_tcp: copy long header into packet\n"));
    BCOPY(&cs->cs_ip, cp, cs->cs_hlen);

    /* recompute the ip header checksum */
    {
	/*register u_short *bp = (u_short *)cp;*/
	register u_short *bp = (u_short *)&cs->cs_ip;

	for (changes = 0; hlen > 0; hlen -= 2)
	    changes += ntohs(*bp++);
	changes = (changes & 0xffff) + (changes >> 16);
	changes = (changes & 0xffff) + (changes >> 16);
	changes = htons(~changes);
	bp = &((struct ip_header *)cp)->checksum;
	((u_char *)bp)[0] = changes >> 8;
	((u_char *)bp)[1] = changes;
    }

    debug(("<sl_uncompress_tcp: done\n"));
    return m;

bad:
    comp->sls_i_error++;
    comp->flags |= SLF_TOSS;

    debug(("<sl_uncompress_tcp: bad packet\n"));
    return NULL;
}

/* Appendix A.4  Initialisation */

void
sl_compress_init(comp, rslots, tslots)
struct slcompress *comp;
int rslots;
int tslots;
{
    register u_int i;
    register struct cstate *tstate = NULL;

    /*
     * Clean out any junk left from the last time the line was used.
     */
    memset(comp, 0, sizeof(*comp));
    if ( rslots > 0  &&  rslots < 256 ) {
	comp->rstate = calloc( rslots, sizeof(struct cstate) );
	comp->rslot_limit = rslots - 1;
    }

    if ( tslots > 0  &&  tslots < 256 ) {
	tstate = calloc( tslots, sizeof(struct cstate) );
	comp->tstate = tstate;
	comp->tslot_limit = tslots - 1;
    }
    /*
     * Link the transmit states into a circular list.
     */
    if (tstate > 0) {
	for (i = comp->tslot_limit; i > 0; --i) {
	    tstate[i].cs_id = i;
	    tstate[i].cs_next = &tstate[i - 1];
	}
	tstate[0].cs_id = 0;
	tstate[0].cs_next = &tstate[comp->tslot_limit];
	comp->last_cs = &tstate[0];
    }
    /*
     * Make sure we don't accidentally do CID compression
     * (assumes MAX_STATES < 255).
     */
    comp->last_recv = NO_CID;
    comp->last_xmit = NO_CID;
    /*
     * Set proper flags
     */
    comp->flags = SLF_TOSS | SLF_ALLOWED;
#ifdef TRYCOUNT
    if (tslots > 0)
	comp->on = TRYCOUNT;
#else
    if (tslots > 0)
	comp->flags |= SLF_ON;
#endif
}

void
sl_compress_deinit(comp)
struct slcompress *comp;
{
    if (comp->rstate)
	free(comp->rstate);
    if (comp->tstate)
	free(comp->tstate);
}
