/*-------------------------------------------------------------------------*\
|* File name:	SLC_COMP.C						Revision date:	1996.09.14 *|
|* Revised by:	Ulf Ronald Andersson			Revision start:	1996.08.02 *|
|* Original by:	Steve Adam & Dan Ackerman								   *|
\*-------------------------------------------------------------------------*/

/*-------------------------------------------------------------------------*\
|* URAn Revision history.												   *|
|* 1996.09.02:	Revised ENCODE macro and its usage						   *|
|* 1996.09.02:	Added function VJ_enco									   *|
|* 1996.09.14:	Corrected some packing variables for signed calculations   *|
\*-------------------------------------------------------------------------*/

#include <stdio.h>      /* Included for NULL.  Might change that later...   */
#include <tos.h>
#include <string.h>
#include <stdlib.h>

#include "globdefs.h"
#include "cslip_hd.h"

/*-------------------------------------------------------------------------*\
|*			ENCODE macro for VJ encoding								   *|
\*-------------------------------------------------------------------------*/

/* URAn NB:	I've abolished ENCODEZ.  ENCODE now does the same, but
 *			has also been amended to always use unsigned calculation,
 *			thus allowing for all possible 16 bit values.
 */

#define ENCODE(n) \
{	if ((uint16)(n) > 255 || (uint16)(n) == 0) \
	{	*cp++ = 0; \
		*cp++ = (uint16)(n) >> 8; \
	} \
	*cp++ = (uint8)(n); \
}

/*-------------------------------------------------------------------------*\
|*			Function "slc_comp"											   *|
\*-------------------------------------------------------------------------*/

unsigned char
slc_compress (b, comp, compress_cid)
	GPKT *b;
	struct slcompress *comp;
	long compress_cid;
{
	register struct cstate *cs = comp->last_cs->cs_next;
	register struct ip_header *ip = (struct ip_header *)b->pip;
	register uint32 hlen = ip->ihl;
	register TCP_HDR *oth;
	TCP_HDR *th;
	register uint32 deltaS, deltaA;
	register int16 changes = 0; /* int16 */
	unsigned char new_seq[16];
	unsigned 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). I fixed this it can be not TCP
	 */
	 
	/* Bail if this packet isn't TCP, or is an IP fragment */
	if(ip->ptcl != IPPROTO_TCP || ip->ofst != 0 || ip->ofst & ~FLAG_DF || b->ip_len < 40)
	{ 
		/* Send as regular IP */
		return TYPE_IP;
	}

	/* Extract TCP header */
	th = (TCP_HDR *)b->mp;

	/*  Bail if the TCP packet isn't `compressible' (i.e., ACK isn't set or
	 *  some other control bit is set).
	 */
	if(th->f_syn || th->f_fin || th->f_rst || !th->f_ack){
		/* TCP connection stuff; send as regular IP */
		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.
	 */
	/*INCR (sls_packets);*/

	if (ip->s_ip != cs->cs_ip.s_ip ||
	    ip->d_ip != cs->cs_ip.d_ip ||
	   *(long *)th != ((long *)&cs->cs_ip)[cs->cs_ip.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.
		 */

		struct cstate *lcs;
		struct cstate *lastcs = comp->last_cs;

		do {
			lcs = cs; 
			cs = cs->cs_next;
			
			/*INCR (sls_searches);*/

			if (ip->s_ip == cs->cs_ip.s_ip &&
			    ip->d_ip == cs->cs_ip.d_ip &&
			    *(long *)th == ((long *)&cs->cs_ip)[cs->cs_ip.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.
		 */

		/*INCR (sls_misses);*/
		comp->last_cs = lcs;

		hlen += th->ofst*4;
		hlen <<= 2;
		goto uncompressed;

	found:
		/*
		 * Found it -- move to the front on the connection list.
		 */

		if (cs == lastcs)
		{		comp->last_cs = lcs;
		}
		else 
		{		lcs->cs_next = cs->cs_next;
				cs->cs_next = lastcs->cs_next;
				lastcs->cs_next = cs;
		}
	}	/* Ends very long if statement */

	/*
	 * 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 = (TCP_HDR *)&((long *)&cs->cs_ip)[hlen];
	deltaS = hlen;
	hlen += th->ofst*4;
	hlen <<= 2;

	if (((unsigned short *)ip)[0] != ((unsigned short *)&cs->cs_ip)[0] ||
	    ((unsigned short *)ip)[3] != ((unsigned short *)&cs->cs_ip)[3] ||
	    ((unsigned short *)ip)[4] != ((unsigned short *)&cs->cs_ip)[4] ||
	    th->ofst*4 != oth->ofst*4 ||
	    (deltaS > 5 &&
	     BCMP(ip + 1, &cs->cs_ip + 1, (deltaS - 5) << 2)) ||
	    (th->ofst*4 > 5 &&
	     BCMP(th + 1, oth + 1, (th->ofst*4 - 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->f_urg)
	{	deltaS = th->urgent;
		ENCODE(deltaS);
		changes |= NEW_U;
	}
	else
		if (th->urgent != oth->urgent)
		{
		/* 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 = (uint16)(th->window - oth->window)) != 0)
	{
		ENCODE(deltaS);
		changes |= NEW_W;
	}

	if ((deltaA = th->ack - oth->ack)!=0L)
	{
		if (deltaA > 0x0000ffff)
			goto uncompressed;
		ENCODE(deltaA);
		changes |= NEW_A;
	}

	if ((deltaS = th->seq - oth->seq)!=0L) 
	{
		if (deltaS > 0x0000ffff)
			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->len != cs->cs_ip.len && cs->cs_ip.len == hlen)
			break;
		goto uncompressed;


		/* (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 == cs->cs_ip.len - hlen)
		{	/* special case for echoed terminal traffic */
			changes = SPECIAL_I;
			cp = new_seq;
		}
		break;

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

	deltaS = ip->id - cs->cs_ip.id;
	if (deltaS != 1) {
		ENCODE(deltaS);
		changes |= NEW_I;
	}
	if (th->f_psh)
		changes |= TCP_PUSH_BIT;
	/*
	 * Grab the cksum before we overwrite it below.  Then update our
	 * state with this packet's header.
	 */
	deltaA = th->sum;
	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 one byte for the change
	 * mask, one for the connection id and two for the tcp checksum.
	 * So, (cp - new_seq) + 4 bytes of header are needed.  hlen is how
	 * many bytes of the original packet to toss so subtract the two to
	 * get the new packet size.
	 */
	deltaS = cp - new_seq;
	cp = (unsigned char *)ip;
	if (compress_cid == 0 || 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;
	}
	b->fp += hlen;
	*cp++ = deltaA >> 8;
	*cp++ = deltaA;
	BCOPY(new_seq, cp, deltaS);
	/*INCR (sls_compressed);*/

	return (TYPE_COMPRESSED_TCP);

	/*
	 * 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:
	BCOPY(ip, &cs->cs_ip, hlen);
	ip->ptcl = cs->cs_id;
	comp->last_xmit = cs->cs_id;
	
	return (TYPE_UNCOMPRESSED_TCP);
}

/*-------------------------------------------------------------------------*\
|*			Function "VJ_enco"											   *|
\*-------------------------------------------------------------------------*/

void
VJ_enco(cpp, data)
	void	**cpp;
	uint16	data;
{	octet	*cp = *cpp;
	ENCODE(data);
	*cpp = cp;
}
/*-------------------------------------------------------------------------*\
|* End of file:	SLC_COMP.C												   *|
\*-------------------------------------------------------------------------*/
