#include "global.h"
#include "timer.h"
#include "mbuf.h"
#include "netuser.h"
#include "internet.h"
#include "tcp.h"

struct tcb *tcbs[NTCB];
int16 tcp_mss = DEF_MSS;	/* Maximum segment size to be sent with SYN */
int32 tcp_irtt = DEF_RTT;	/* Initial guess at round trip time */
int32 tcp_timout = SEC2TICK(TIMOUT); /* Transient state timeout */

/* Lookup connection, return TCB pointer or NULLTCB if nonexistant */
struct tcb *
lookup_tcb(conn)
struct connection *conn;
{
	register struct tcb *tcb;
	int16 hash_tcb();

	tcb = tcbs[hash_tcb(conn)];
	while(tcb != NULLTCB){
		/* Yet another structure compatibility hack */
		if(conn->local.address == tcb->conn.local.address
		 && conn->remote.address == tcb->conn.remote.address
		 && conn->local.port == tcb->conn.local.port
		 && conn->remote.port == tcb->conn.remote.port)
			break;
		tcb = tcb->next;
	}
	return tcb;
}

/* Create a TCB, return pointer. Return pointer if TCB already exists. */
struct tcb *
create_tcb(conn)
struct connection *conn;
{
	register struct tcb *tcb;
	void tcp_timeout(),reset_tcp(),tcp_msl();
	void link_tcb();

	if((tcb = lookup_tcb(conn)) != NULLTCB)
		return tcb;
	if((tcb = (struct tcb *)calloc(1,sizeof (struct tcb))) == NULLTCB)
		return NULLTCB;
	ASSIGN(tcb->conn,*conn);

	tcb->cwind = tcb->mss = tcp_mss;
	tcb->ssthresh = 65535;
	tcb->srtt = tcp_irtt;
	/* Initialize timer intervals */
	tcb->timer.start = MS2TICK(tcb->srtt);
	tcb->timer.func = tcp_timeout;
	tcb->timer.arg = (char *)tcb;
	tcb->timeout.start = tcp_timout;
	tcb->timeout.func = reset_tcp;
	tcb->timeout.arg = (char *)tcb;
	tcb->rtt_timer.start = MAX_TIME; /* Largest possible value */

	link_tcb(tcb);
	return tcb;
}

/* Close our TCB */
void
close_self(tcb,reason)
register struct tcb *tcb;
char reason;
{
	struct reseq *rp,*rp1;

	stop_timer(&tcb->timer);
	stop_timer(&tcb->timeout);
	stop_timer(&tcb->rtt_timer);
	tcb->reason = reason;

	/* Flush reassembly queue; nothing more can arrive */
	for(rp = tcb->reseq;rp != NULLRESEQ;rp = rp1){
		rp1 = rp->next;
		free_p(rp->bp);
		free((char *)rp);
	}
	tcb->reseq = NULLRESEQ;
	setstate(tcb,CLOSED);
}

/* Determine initial sequence number */
int32
iss()
{
	static int32 seq;

	seq += 250000;
	return seq;
}

/* Sequence number comparisons
 * Return true if x is between low and high inclusive,
 * false otherwise
 */
int
seq_within(x,low,high)
register int32 x,low,high;
{
	if(low <= high){
		if(low <= x && x <= high)
			return 1;
	} else {
		if(low >= x && x >= high)
			return 1;
	}
	return 0;
}
int
seq_lt(x,y)
register int32 x,y;
{
	return (long)(x-y) < 0;
}
int
seq_le(x,y)
register int32 x,y;
{
	return (long)(x-y) <= 0;
}
int
seq_gt(x,y)
register int32 x,y;
{
	return (long)(x-y) > 0;
}
int
seq_ge(x,y)
register int32 x,y;
{
	return (long)(x-y) >= 0;
}

/* Hash a connect structure into the hash chain header array */
static int16
hash_tcb(conn)
struct connection *conn;
{
	register int16 hval;

	/* Compute hash function on connection structure */
	hval = hiword(conn->remote.address);
	hval ^= loword(conn->remote.address);
	hval ^= hiword(conn->local.address);
	hval ^= loword(conn->local.address);
	hval ^= conn->remote.port;
	hval ^= conn->local.port;
	hval %= NTCB;
	return hval;
}
/* Insert TCB at head of proper hash chain */
void
link_tcb(tcb)
register struct tcb *tcb;
{
	register struct tcb **tcbhead;
	int16 hash_tcb();
	/*char i_state;*/

	tcb->prev = NULLTCB;
	/*i_state = disable();*/
	tcbhead = &tcbs[hash_tcb(&tcb->conn)];
	tcb->next = *tcbhead;
	if(tcb->next != NULLTCB){
		tcb->next->prev = tcb;
	}
	*tcbhead = tcb;
	/*restore(i_state);*/
}
/* Remove TCB from whatever hash chain it may be on */
void
unlink_tcb(tcb)
register struct tcb *tcb;
{
	register struct tcb **tcbhead;
	int16 hash_tcb();
	/*char i_state;*/

	/*i_state = disable();*/
	tcbhead = &tcbs[hash_tcb(&tcb->conn)];
	if(*tcbhead == tcb)
		*tcbhead = tcb->next;	/* We're the first one on the chain */
	if(tcb->prev != NULLTCB)
		tcb->prev->next = tcb->next;
	if(tcb->next != NULLTCB)
		tcb->next->prev = tcb->prev;
	/*restore(i_state);*/
}
void
setstate(tcb,newstate)
register struct tcb *tcb;
register char newstate;
{
	register char oldstate;
	int16 window;

	oldstate = tcb->state;
	switch (tcb->state = newstate)
	{
	case ESTABLISHED:
	case TIME_WAIT:
		stop_timer(&tcb->timeout);	/* Left transient state */
		break;
	case FINWAIT2:
	case CLOSED:
		start_timer(&tcb->timeout);	/* Enter transient state */
		break;
	}
	if(tcb->s_upcall){
		(*tcb->s_upcall)(tcb,oldstate,newstate);
	}
	/* Notify the user that he can begin sending data */
	if(tcb->t_upcall && newstate == ESTABLISHED){
		window = min(tcb->cwind,tcb->snd.wnd);
		window = min(window,tcb->window);
		window = max(1,window);
		(*tcb->t_upcall)(tcb,window - tcb->sndcnt);
	}
}
/* Convert TCP header in host format into mbuf ready for transmission,
 * link in data (if any), and compute checksum
 */
struct mbuf *
htontcp(tcph,data,ph)
struct tcp *tcph;
struct mbuf *data;
struct pseudo_header *ph;
{
	int16 hdrlen;
	struct mbuf *bp;
	register char *cp;
	int16 csum;

	hdrlen = (tcph->mss != 0) ? TCPLEN + MSS_LENGTH : TCPLEN;

	if((bp = pushdown(data,hdrlen)) == NULLBUF){
		free_p(data);
		return NULLBUF;
	}
	cp = bp->data;
	cp = put16(cp,tcph->source);
	cp = put16(cp,tcph->dest);
	cp = put32(cp,tcph->seq);
	cp = put32(cp,tcph->ack);
	*cp++ = hdrlen << 2;	/* Offset field */
	*cp++ = tcph->flags;
	cp = put16(cp,tcph->wnd);
	*cp++ = 0;	/* Zero out checksum field */
	*cp++ = 0;
	cp = put16(cp,tcph->up);

	if(tcph->mss != 0){
		*cp++ = MSS_KIND;
		*cp++ = MSS_LENGTH;
		cp = put16(cp,tcph->mss);
	}
	csum = cksum(ph,bp,ph->length);
	cp = &bp->data[16];	/* Checksum field */
	*cp++ = csum >> 8;
	*cp = csum;

	return bp;
}
/* Pull TCP header off mbuf */
int
ntohtcp(tcph,bpp)
struct tcp *tcph;
struct mbuf **bpp;
{
	int16 hdrlen;
	int16 i,optlen;
	char tcphead[20];

	if(pullup(bpp,tcphead,20) != 20)
		return -1;

	tcph->source = get16(tcphead);
	tcph->dest = get16(tcphead + 2);
	tcph->seq = get32(tcphead + 4);
	tcph->ack = get32(tcphead + 8);
	hdrlen = (tcphead[12] & 0xf0) >> 2;
	tcph->flags = tcphead[13];
	tcph->wnd = get16(tcphead + 14);
	tcph->up = get16(tcphead + 18);
	tcph->mss = 0;

	/* Check for option field. Only space for one is allowed, but
	 * since there's only one TCP option (MSS) this isn't a problem
	 */
	if(hdrlen < TCPLEN)
		return -1;	/* Header smaller than legal minimum */
	if(hdrlen == TCPLEN)
		return hdrlen;	/* No options, all done */

	if(hdrlen > len_mbuf(*bpp) + TCPLEN){
		/* Remainder too short for options length specified */
		return -1;
	}
	/* Process options */
	for(i=TCPLEN; i < hdrlen;){
		switch(pullchar(bpp)){
		case EOL_KIND:
			i++;
			goto eol;	/* End of options list */
		case NOOP_KIND:
			i++;
			break;
		case MSS_KIND:
			optlen = pullchar(bpp);
			if(optlen == MSS_LENGTH)
				tcph->mss = pull16(bpp);
			i += optlen;
			break;
		}
	}
eol:
	/* Get rid of any padding */
	if(i < hdrlen)
		pullup(bpp,NULLCHAR,hdrlen - i);
	return hdrlen;
}
