/*
*   TCP routines
*
****************************************************************************
*                                                                          *
*      part of:                                                            *
*      TCP/UDP/ICMP/IP Network kernel for NCSA Telnet                      *
*      by Tim Krauskopf                                                    *
*                                                                          *
*      National Center for Supercomputing Applications                     *
*      152 Computing Applications Building                                 *
*      605 E. Springfield Ave.                                             *
*      Champaign, IL  61820                                                *
*                                                                          *
****************************************************************************
*   Tim Krauskopf          Fall 1986
*    mods for int16/int32  2/88
*/
#include "pcdefs.h"
#include "mbuf.h"
#include "protocol.h"
#include "data.h"
#include "config.h"
#include "funcdef.h"
#include "tcp.h"

static void _near do_options(struct tcpport far *p, union rawether near *pkt);

/************************************************************************
*  tcpinterpret
*  This is called when a packet comes in, passes IP checksumming and is
*  of the TCP protocol type.  Check and see if we are expecting it and
*  where the data should go.
*/
void _near _fastcall
tcpinterpret(pkt, len)
union rawether near *pkt;
u_int len;			/* length of packet, including header */
{
	int	i;
	struct tcpport far *p;
	int hlen;

	/* Subtract the header length. */
	hlen = (rawtcp(pkt).t.hlen >> 2) & 0x3c;
	len -= hlen;
/*
*  find the port which is associated with the incoming packet
*  First try open connections, then try listeners
*/
	for (i = 0; i < NPORTS; i++) {
		p = (struct tcpport far *)portlist[i];
		if (!p)
			continue;
		if (p->type != SOCK_STREAM)
			continue;
		if (p->state == SLISTEN)
			continue;
		if (p->inport == rawtcp(pkt).t.dest &&
		    p->outport == rawtcp(pkt).t.source &&
		    p->outpkt.i.ipdest == rawtcp(pkt).i.ipsource) {
			(void) tcpdo(p, pkt, len);
			return;
		}
	}
/*
*  check to see if the incoming packet should go to a listener
*/
	if (rawtcp(pkt).t.flags & TRESET)	/* per RFC 793 */
		return;

	for (i = 0; i < NPORTS; i++) {
		p = (struct tcpport far *)portlist[i];
		if (!p)
			continue;
		if (p->type != SOCK_STREAM)
			continue;
		if (p->state == SLISTEN && rawtcp(pkt).t.flags & TSYN &&
		   p->inport == rawtcp(pkt).t.dest) {
			p->outwin = ntohs(rawtcp(pkt).t.window);
			/* ack the SYN */
			p->outack = ntohl(rawtcp(pkt).t.seq) + 1L;
			_disable();
			p->outseq = N_CLICKS();
			_enable();
			p->outport = rawtcp(pkt).t.source;
			p->outpkt.t.dest = rawtcp(pkt).t.source;
			p->outpkt.t.flags = TSYN | TACK;
/*
*  note that the maxmimum segment size is installed by 'SoSocket()'
*/

/*
*  initialize all of the low-level transmission stuff (IP and lower)
*/
			p->outpkt.i.ipdest = rawtcp(pkt).i.ipsource;
			if (!slip_mode)
			    (void) memcpy((u_char far *)p->outpkt.d.dest,
				(u_char far *)rawtcp(pkt).d.me, DADDLEN);
			p->state = SSYNRS;	/* syn received */

			/* If the destination host is not on the same network,
			 * send a smaller MSS option.
			 */
			if ((p->outpkt.i.ipdest & Scon.snetmask) !=
			    (Scon.myip & Scon.snetmask)) {
				*(u_short _far *)&p->outpkt.x.options[2] =
				    htons(MSS_SMALL);
			}

			if (hlen > sizeof(struct tcph))
				do_options(p, pkt);
			if (len)	/* data in first packet */
				(void) estab1986(p, pkt, len);
			(void) tcpsend(p, 4);
			return;
		}
	}
dfputs("no matching ports\r\n");
/*
*  no matching port was found to handle this packet, reject it
*/
	(void) tcpreset(pkt, len);
}

/**********************************************************************/
/*  tcpdo
*  Looking at the port structure for the destination port, deliver
*  the incoming packet.
*/
void near
tcpdo(p, pkt, len)
struct tcpport far *p;
union rawether near *pkt;
u_int len;		/* size of data field */
{
	u_long	seq, ak;

	seq = ntohl(rawtcp(pkt).t.seq);
	if (rawtcp(pkt).t.flags & TRESET && p->state != SSYNS &&
	    p->outack >= seq && (u_int)(p->outack - seq) < p->credit) {
dfputs("got reset");
		if (p->state == SSYNRS)
			p->state = SLISTEN;
		else
			p->state = SCLOSED;
		return;
	}
	ak = ntohl(rawtcp(pkt).t.ack);
	switch (p->state) {
	    case SSYNR:
	    case SSYNRS:
		if (rawtcp(pkt).t.flags & TACK) {
			/* better be for me */
			if (ak != p->outseq + 1) {
				(void) tcpreset(pkt, len);
				break;
			}
		} else {
dfputs("waiting for ACK of SYN");
			(void) tcpsend(p, 4);
			break;			/* not the right one */
		}
		p->outseq = ak;
		p->outpkt.t.hlen = (sizeof(struct tcph) << 2) & 0xf0;
		p->outwin = ntohs(rawtcp(pkt).t.window);
		p->outpkt.t.flags = TACK | TPUSH;
		if (((rawtcp(pkt).t.hlen >> 2) & 0x3c) > sizeof(struct tcph))
			do_options(p, pkt);
		if (len) {
dfputs("SSYNRS: calling estab\r\n");
			estab1986(p, pkt, len);
		}
		p->state = SEST;
		break;
	    case SEST:
		if (rawtcp(pkt).t.flags & TACK) {
			(void) ackcheck(p, pkt);
			if (p->flag & O_CLOSED) {
dfputs("SEST: sending TFIN\r\n");
				p->outpkt.t.flags = TACK | TFIN;
				(void) tcpsend(p, 0);
				p->state = SFW1;
			}
		}
		estab1986(p, pkt, len);
		if (p->flag & O_GOTALL) {
dfputs("SEST: gotall -> SCWAIT\r\n");
			p->state = SCWAIT;
		}
		break;
	    case SSYNS:	/* check to see if it ACKS correctly */
		if (rawtcp(pkt).t.flags & TRESET && ak == p->outseq + 1L) {
dfputs("SSYNS:got reset");
			p->state = SCLOSED;
			break;
		}
		if (rawtcp(pkt).t.flags & TSYN) {
			if (rawtcp(pkt).t.flags & TACK) {
				p->outseq = ak;
				p->outack = seq + 1L;
				p->outpkt.t.flags = TACK | TPUSH;
				p->outwin = ntohs(rawtcp(pkt).t.window);
				p->outpkt.t.hlen = (sizeof(struct tcph) << 2) &
				    0xf0;
				if (((rawtcp(pkt).t.hlen >> 2) & 0x3c) >
				    sizeof(struct tcph) &&
				    *(u_int near *)rawtcp(pkt).x.options ==
				    htons(0x24)) {
dfputs("got mss");
#ifdef notdef
					i = ntohs(*(u_int near *)
					    &rawtcp(pkt).x.options[2]);
					if (i < p->sendsize)
						p->sendsize = i;
#endif
				}
				(void) tcpsend(p, 0);
				p->state = SEST;
			} else {
dfputs("SSYNS: rcvd SYN w/o ACK\r\n");
				p->outack = seq + 1L;
				p->outpkt.t.flags = TACK | TSYN;
				(void) tcpsend(p, 4);
				p->state = SSYNR;
			}
		} else {
dfputs("SSYNS: send reset\r\n");
			(void) tcpreset(pkt, len);
		}
		break;
	    case SCWAIT:
		if (rawtcp(pkt).t.flags & TACK) {
			(void) ackcheck(p, pkt);
		}
		if (p->flag & O_CLOSED) {
dfputs("SCWAIT: sending TFIN\r\n");
			p->outpkt.t.flags = TACK | TFIN;
			(void) tcpsend(p, 0);
			p->state = SLAST;
		}
		break;
	    case SLAST:
		if (ak != p->outseq + 1L) {
dfputs("SLAST: reacking FIN\r\n");
			(void) tcpsend(p, 0);
		} else {
dfputs("SLAST: CLOSED\r\n");
			p->state = SCLOSED;
		}
		break;
	    case SFW1:		/* waiting for ACK of FIN */
		if (rawtcp(pkt).t.flags & TACK) {
			if (ak == p->outseq + 1L) {
				p->outseq++;
				p->outpkt.t.flags = TACK;
				p->state = SFW2;
			}
		}
		/* FALL THROUGH */
	    case SFW2:
		estab1986(p, pkt, len);
		if (p->flag & O_GOTALL) {
dfputs("SFW2: got FIN\r\n");
			/* cause last ACK to be sent */
			(void) tcpsend(p, 0);
			if (p->state == SFW2)
				p->state = STWAIT;
			else
				p->state = SCLOSING;
		}
		break;
	    case SCLOSING:		/* want ACK of FIN */
		if (rawtcp(pkt).t.flags & TACK && ak == p->outseq + 1L) {
dfputs("SCLOSING: got ACK of FIN\r\n");
			p->state = STWAIT;	/* time-wait state next */
		} else {
dfputs("SCLOSING: resending FIN\r\n");
			(void) tcpsend(p, 0);
		}
		break;
	    case STWAIT:		/* ack FIN again? */
		if (rawtcp(pkt).t.flags & TFIN) {
dfputs("STWAIT: re ACKing\r\n");
			(void) tcpsend(p, 0);
			break;
		}
		break;			
	    case SCLOSED:
		break;
	}
}

/**********************************************************************/
/* tcpreset
*  Send a reset packet back to sender
*  Use the packet which just came in as a template to return to
*  sender.  Fill in all of the fields necessary and pkxmit it back.
*/
void near
tcpreset(pkt, len)
union rawether near *pkt;
u_int	len;
{
	u_long	oack;

	if (rawtcp(pkt).t.flags & TRESET)	/* don't reset a reset */
		return;
/*
*  swap TCP layer portions for sending back
*/
	if (rawtcp(pkt).t.flags & TACK) {
		tcpout.t.seq = rawtcp(pkt).t.ack; /* ack becomes next seq # */
		tcpout.t.ack = 0L;		/* ack # is 0 */
		tcpout.t.flags = TRESET;
	} else {
		oack = ntohl(rawtcp(pkt).t.seq) + (u_long)len;
		if (rawtcp(pkt).t.flags & TSYN)
			oack++;
		tcpout.t.ack = htonl(oack);
		tcpout.t.seq = 0L;
		tcpout.t.flags = TRESET | TACK;
	}
	tcpout.t.source = rawtcp(pkt).t.dest;
	tcpout.t.dest = rawtcp(pkt).t.source;
	tcpout.t.hlen = (sizeof(struct tcph) << 2) & 0xf0; /* header len */
	tcpout.t.window = 0;
	tcpout.t.urgent = 0;

	tcpout.i.ipdest = rawtcp(pkt).i.ipsource;
	tcpout.i.ipsource = Scon.myip; 
	tcpout.i.protocol = PROTTCP;

/*
*  create pseudo header for checksum
*/
	itcps.proto = PROTTCP;
	itcps.tcplen = htons(sizeof(struct tcph));
	(void) memcpy((u_char near *)&itcps.source,
	    (u_char near *)&tcpout.i.ipsource, 2 * sizeof(u_long));
	tcpout.t.check = 0;	
	tcpout.t.check = tcpcheck((struct pseudotcp far *)&itcps,
	    (struct tcph far *)&tcpout.t, sizeof(struct tcph));
/*
*  IP and data link layers
*/	
	tcpout.i.tlen = htons(sizeof(struct iph) + sizeof(struct tcph));
	tcpout.i.ident = htons(nnipident);
	nnipident++;
	tcpout.i.check = 0;
	tcpout.i.check = ipcheck(&tcpout.i, sizeof(struct iph));
	if (!slip_mode)
	    (void) memcpy((u_char near *)tcpout.d.dest,
		(u_char near *)rawtcp(pkt).d.me, DADDLEN);
	
	(void) pkxmit((u_char far *)&tcpout,
	    sizeof(struct ether) + sizeof(struct iph) + sizeof(struct tcph));
}

/***************************************************************************/
/*  tcpsend
*     transmits a TCP packet.  
*
*   For IP:
*      sets ident,check,totallen
*   For TCP:
*      sets seq and window from port information,
*		fills in the pseudo header and computes the checksum.
*      Assumes that all fields not filled in here are filled in by the
*      calling proc or were filled in by makeport(). 
*      (see all inits in protinit)
*
*/
void near
tcpsend(p, dlen)
struct tcpport far *p;
u_int dlen;
{

	p->outpkt.t.window = htons(p->credit);
	p->outpkt.t.seq = htonl(p->outseq);
	p->outpkt.t.ack = htonl(p->outack);
	dlen += sizeof(struct tcph);
	(void) memcpy((u_char far *)&otcps.source,
	    (u_char far *)&p->outpkt.i.ipsource, 2 * sizeof(u_long));
	otcps.proto = PROTTCP;
	otcps.tcplen = htons(dlen);
	p->outpkt.t.check = 0;
	p->outpkt.t.check = tcpcheck((struct pseudotcp far *)&otcps,
	    (struct tcph far *)&p->outpkt.t, dlen);

	dlen += sizeof(struct iph);
	p->outpkt.i.ident = htons(nnipident);
	nnipident++;
	p->outpkt.i.tlen = htons(dlen);
	p->outpkt.i.check = 0;			/* install checksum */
	p->outpkt.i.check = iphcheck((u_long)&p->outpkt.i);	/* MSC crock */
	_disable();
	p->lasttime = N_CLICKS();
	_enable();

	(void) pkxmit((u_char far *)&p->outpkt, sizeof(struct ether) + dlen);
}

/***************************************************************************/
/*  ackcheck
*   take an incoming packet and see if there is an ACK for the outgoing
*   side.  Use that ACK to dequeue outgoing data.
*/
void near
ackcheck(p, pkt)
struct tcpport _far *p;
union rawether _near *pkt;
{
	u_long ak;
	u_long rttl;
	u_int outwin;
	int i;

	outwin = ntohs(rawtcp(pkt).t.window); /* allowable xmit size */
/*
*  Need to add code to check for wrap-around of sequence space
*  for ak.  ak - p->out.ack may be affected by sequence wraparound.
*  If you have good, efficient code for this, please send it to me.
*
*  If ak is not increasing (above p->out.nxt) then we should assume
*  that it is a duplicate packet or one of those stupid keepalive
*  packets that 4.2 sends out.
*/
	ak = ntohl(rawtcp(pkt).t.ack);		/* other side's ACK */
	i = (int)(ak - p->outseq);
	if (i > 0) {
	    p->outseq = ak;
	    if (i < (int)p->outfree) {
		p->outfree -= i;
		/* send more */
		p->wait_tx = 1;
		(void) memcpy((u_char _far *)p->dataout,
		    (u_char _far *)&p->dataout[i], p->outfree);
	    } else {
		p->outfree = 0;
		if (p->flag & O_CLOSING)
			p->flag |= O_CLOSED;
/*
*  Check to see if this acked our most recent transmission.  If so, adjust
*  the RTO value to reflect the newly measured RTT.  This formula reduces
*  the RTO value so that it gradually approaches the most recent round
*  trip measurement.  When a packet is retransmitted, this value is
*  doubled (exponential backoff).
*/
		rttl = n_clicks() - p->lasttime;
		if (p->lasttime && p->rto > MINRTO) {
			/* smoothing function */
			i = (int)(((long)(p->rto-MINRTO)*3L + rttl + 1L) >> 2);
			p->rto = i+MINRTO;
		}
	    }
	}
	else if (i < 0)
		dfputs("panic acked > 32767 bytes\r\n");
	else if (!p->outwin && outwin)
		p->wait_tx = 1;
	p->outwin = outwin;
}

/*
*  estab1986
*   take a packet which has arrived for an established connection and
*   put it where it belongs.
*/
void near
estab1986(p, pkt, len)
struct tcpport far *p;
union rawether near *pkt;
int len;
{
	int	slen;
	int	credit;
	u_long	seq;

	seq = ntohl(rawtcp(pkt).t.seq);
	if (len > 0) {
		slen = (int)(p->outack - seq);
		if (rawtcp(pkt).t.flags & TSYN)
			--slen;
		if (slen) {
			if (slen > 0) {
				len -= slen;
				if (len <= 0) {
dfputs("estab: dont need\r\n");
					/* don't need it, retransmit ack */
					p->wait_tx = 1;
					return;
				}
			} else {
dfputs("estab: slen < 0\r\n");
				p->wait_tx = 1;
				return;
			}
		}
/*
*  If we have room in the window, update the ACK field values
*/
		credit = WINDOWSIZE - p->infree;
		if (credit < len) {
			len = credit;
			credit = 0;
if (!len)
dfputs("input window is zero\r\n");
		} else
			credit -= len;

		if (len) {
			(void) memcpy((u_char _far *)&p->datain[p->infree],
			    (u_char _far *)rawtcp(pkt).x.data + slen, len);
			p->infree += len;
			p->outack += (u_long)len;	/* new ack value */
			p->credit = credit;
		}
		if (!p->wait_tx)
			p->wait_tx = 20;
		else if (p->wait_tx > 3)
			p->wait_tx -= 3;
	}
	if (rawtcp(pkt).t.flags & TFIN) {
		seq += len;
		if (p->outack == seq) {
dfputs("estab: setting O_GOTALL\r\n");
			p->flag |= O_GOTALL;
			p->outack++;	/* ack the fin */
		}
		p->wait_tx = 2;		/* send ack */
	}
}

static void _near
do_options(p, pkt)
struct tcpport far *p;
union rawether near *pkt;
{
	u_short i;
	u_char op;
	u_char near *cp = rawtcp(pkt).x.options;

	while ((op = *cp++) != 0) {
	    if (op == 2) {
		cp++;	/* skip len == 4 */
		i = ntohs(*(u_short near *)cp);
		if (i < p->outsize)	/* we have our own limits too */
			p->outsize = i;
		cp += 2;
	    }
	}
}
