/*--------------------------------------------------------------------------*/
/*	File name:	ARRIVE.C						Revision date:	1999.10.16	*/
/*	Revised by:	Ulf Ronald Andersson			Revision start:	1999.01.14	*/
/*	Created by:	Peter Rottengatter				Creation date:	1997.02.19	*/
/*--------------------------------------------------------------------------*/
/*	Project:	TCP high level protocol module for STinG					*/
/*	Module:		Segment handler.  Deals with arriving data.					*/
/*--------------------------------------------------------------------------*/
#include <tos.h>
#include <stdio.h>
#include <string.h>
#include <sting\transprt.h>
#include <sting\layer.h>
#include <sting\sys\tcp.h>
/*--------------------------------------------------------------------------*/
void		send_sync (CONNEC *connec);
void		process_sync (CONNEC *connec, IP_DGRAM *dgram);
void		process_options (CONNEC *connec, IP_DGRAM *dgram);
void		send_reset (IP_DGRAM *dgram);
void		close_self (CONNEC *connec, int16 reason);

void		do_RTT_calc (CONNEC *conn);
void		update_wind (CONNEC *connec, TCP_HDR *tcph);
int16		trim_segm (CONNEC *connec, IP_DGRAM *dgram, RESEQU **block, int16 flag);
void		add_resequ (CONNEC *connec, RESEQU *block);
void		do_output (CONNEC *connec);

int16	cdecl	TCP_handler (IP_DGRAM *dgram);
void			do_arrive (CONNEC *conn, IP_DGRAM *dgram);


extern	TCP_CONF	my_conf;
extern	CONNEC		*root_list;
/*--------------------------------------------------------------------------*/
/* TCP_handler is the lowest level handler for incoming TCP datagrams.		*/
/* It is logged in with the STinG kernel as IP_handler for TCP protocol.	*/
/* Its main action is to pass datagrams into the 'pending' queue of the		*/
/* connection a datagram is addressed to.  In so doing it will also handle	*/
/* the completion of socket data when a passive connection is activated,	*/
/* but it leaves the state transition to other routines.  TCP_handler also	*/
/* checks the TCP header and checksum and rejects any damaged packets.		*/
/*--------------------------------------------------------------------------*/
int16	cdecl	TCP_handler (IP_DGRAM	*datagram)
{	CONNEC    *connect, *option;
	TCP_HDR   *hdr;
	IP_DGRAM  *walk;
	uint16    len, count, max_mss, work_SR;

	hdr = (TCP_HDR *) datagram->pkt_data;
	len = datagram->pkt_length;

	if	(len < sizeof (TCP_HDR))
	{	my_conf.generic.stat_dropped++;
		return (TRUE);
	}

	if	(check_sum (datagram->hdr.ip_src, datagram->hdr.ip_dest, hdr, len) != 0)
	{	my_conf.generic.stat_dropped++;
		return (TRUE);
	}

	option = NULL;
	for	(connect = root_list; connect; connect = connect->next)
	{	if	(	(hdr->dest_port == connect->local_port)
			&&	(hdr->src_port == connect->remote_port)
			&&	(datagram->hdr.ip_dest == connect->local_IP_address)
			&&	(datagram->hdr.ip_src == connect->remote_IP_address)
			)	/* Here a fitting fully specified connection was found */
			if	(connect->state != TLISTEN)
				break;			/* break with matching active connection	*/
			else			/* else we found a matching passive connection	*/
			{	option = connect;
				continue;				/* loop on to try next connection	*/
			}
		if	(	(hdr->dest_port == connect->local_port)
			&&	((!connect->remote_port)       || (hdr->src_port == connect->remote_port))
			&&	((!connect->local_IP_address)  || (datagram->hdr.ip_dest == connect->local_IP_address))
			&&	((!connect->remote_IP_address) || (datagram->hdr.ip_src != connect->remote_IP_address))
			)	/* Here a fitting passive connection was found */
			option = connect;
	}
	
	if	(	connect == NULL
		&& option != NULL
		&& option->state == TLISTEN
		&& hdr->sync
		)	/* The packet is for a passive connection now to be activated	*/
	{	connect = option;
		connect->local_IP_address  = datagram->hdr.ip_dest;
		connect->remote_IP_address = datagram->hdr.ip_src;
		connect->remote_port       = hdr->src_port;
		if	(connect->info)
		{	connect->info->address.lhost = connect->local_IP_address;
			connect->info->address.rhost = connect->remote_IP_address;
			connect->info->address.rport = connect->remote_port;
			connect->info->status = 0;
		}
		PRTCL_get_parameters (datagram->hdr.ip_src, NULL, & connect->ttl, & connect->mtu);
		max_mss = connect->mtu - sizeof (IP_HDR) - sizeof (TCP_HDR);
		connect->mss = (connect->mss < max_mss) ? connect->mss : max_mss;
	}

	if	(connect == NULL)
	{	send_reset (datagram);
		my_conf.generic.stat_dropped++;
		return (TRUE);
	}
	if	(connect->state == TCLOSED)
	{	send_reset (datagram);
		my_conf.generic.stat_dropped++;
		return (TRUE);
	}

	if	((walk = (IP_DGRAM *) KRmalloc (sizeof (IP_DGRAM))) == NULL)
	{	my_conf.generic.stat_dropped++;
		return (TRUE);
	}
	memcpy (walk, datagram, sizeof (IP_DGRAM));

	datagram->options = datagram->pkt_data = NULL;
	datagram = walk;

	datagram->next = NULL;

	if	(connect->pending)
	{	for (walk = connect->pending; walk->next; walk = walk->next);
		walk->next = datagram;
	}
	else
		connect->pending = datagram;

	return (TRUE);
}
/*--------------------------*/
/* ends:	TCP_handler		*/
/*--------------------------------------------------------------------------*/
/* Function 'do_arrive' below handles all major classification of incoming	*/
/* TCP datagrams for a specified connection. It is called from 'timer_work' */
/* both through time interrupts and through polling functions in the API.	*/
/* It is responsible for most of the traffic-generated state changes, and	*/
/* this is where a passive connection will be activated.  Another of its	*/
/* vital functions is to handle the segment queue storage via 'add_resequ'.	*/
/*--------------------------------------------------------------------------*/
void	do_arrive (CONNEC *conn, IP_DGRAM *datagram)
{	TCP_HDR  *hdr;
	RESEQU   *net_data, temp;
	NDB      *ndb, *work;
	int16  	stored, trim;
	uint16   len;

	hdr = (TCP_HDR *) datagram->pkt_data;
	len = datagram->pkt_length;

	switch (conn->state)		/* For initial rejection of protocol errors */
	{							/* plus some other initial responses		*/
	case TCLOSED :
		send_reset (datagram);
		conn->net_error = E_UA;
		conn->rtrp.mode = FALSE;		/* We got garbage, restart timing	*/
		my_conf.generic.stat_dropped++;
		return;							/*----------------------------------*/
	case TLISTEN :
		if	(hdr->reset)
			return;						/*----------------------------------*/
		if	(hdr->ack)
		{	conn->rtrp.mode = FALSE;	/* We got garbage, restart timing	*/
			send_reset (datagram);
			my_conf.generic.stat_dropped++;
			return;						/*----------------------------------*/
		}
		if	(hdr->sync)
		{	process_sync (conn, datagram);
			send_sync (conn);
			my_conf.con_in++;
			conn->state = TSYN_RECV;			/* state transition !!! */
			if	(len - hdr->offset * 4 > 0 || hdr->fin)
				break;		/* break as more processing below is needed */
			do_output (conn);
		}
		else									/* else !hdr->sync			*/
			my_conf.generic.stat_dropped++;		/* TLISTEN requires sync!	*/
		return;							/*----------------------------------*/
	case TSYN_SENT :
		if	(hdr->ack)
		{	if	(! SEQ_in (hdr->acknowledge, conn->send.ini_sequ + 1, conn->send.next))
			{	conn->rtrp.mode = FALSE;	/* We got garbage, restart timing	*/
				send_reset (datagram);
				conn->net_error = E_UA;
				my_conf.generic.stat_dropped++;
				return;					/*----------------------------------*/
			}
		}
		if	(hdr->reset)
		{	if	(hdr->ack)
			{	conn->rtrp.mode = FALSE;	/* We got garbage, restart timing	*/
				close_self (conn, E_RRESET);
				conn->net_error = E_REFUSE;
			}
			return;						/*----------------------------------*/
		}
		if	(hdr->sync)
			process_sync (conn, datagram);
/*--------------------------------------------------------------------------*/
/* The test and 'process_sync' above used to be in a clause below, but was	*/
/* moved here to allow adaption to newer practices in PREC negotiations		*/
/*--------------------------------------------------------------------------*/

		if	(hdr->ack && PREC (datagram->hdr.tos) != PREC (conn->tos))
		{	conn->rtrp.mode = FALSE;	/* We got garbage, restart timing	*/
			send_reset (datagram);
			conn->net_error = E_UA;
			my_conf.generic.stat_dropped++;		/* reject bad PREC */
			return;						/*----------------------------------*/
		}
		if	(hdr->sync)
		{	if	(hdr->ack)
			{	update_wind (conn, hdr);
				conn->state = TESTABLISH;		/* state transition !!! */
			}
			else
				conn->state = TSYN_RECV;		/* state transition !!! */

			if	(len - hdr->offset * 4 > 0 || hdr->fin)
				break;		/* break as more processing below is needed */

			if	(conn->rtrp.mode)
				do_RTT_calc(conn);
			do_output (conn);
		}

		if	(hdr->ack  &&  conn->ca.cwnd < MAX_WIND)
		{	if	(conn->ca.cwnd <= conn->ca.sstresh)			/* slow start ? */
				conn->ca.cwnd += conn->mss;
			else									/* congestion avoidance	*/
			{	if	(	conn->ca.time == 0L
					||	TIMER_elapsed(conn->ca.time) > conn->rtrp.srtt
					)
				{	conn->ca.time = TIMER_now();
					conn->ca.accu = 0;
				}
				if	(conn->ca.accu < conn->mss)
				{	uint16	aux = conn->mss*conn->mss/conn->ca.cwnd;
					if	(conn->ca.accu+aux > conn->mss)
						aux = conn->mss - conn->ca.accu;
					conn->ca.cwnd += aux;
					conn->ca.accu += aux;
				}
			}
		}

		return;							/*----------------------------------*/
	}	/* ends: 1st	switch (conn->state)	*/

	if	(! hdr->sync)
		process_options (conn, datagram);

	if	(! trim_segm (conn, datagram, & net_data, TRUE))
	{	if	(! hdr->reset)
		{	conn->flags |= FORCE;		/* No delay, opponent is confused !	*/
			conn->rtrp.mode = FALSE;	/* We got garbage, restart timing	*/
			do_output (conn);			/* Send probe to say what we expect	*/
		}
		KRfree (net_data);
		return;							/*----------------------------------*/
	}
	datagram->pkt_data = NULL;

/*--------------------------------------------------------------------------*/
/* Here the packet is known as a valid reply, so this is the correct place	*/
/* for RTT calculation and other related computations.						*/
/* Those jobs were formerly done by 'update_wind', but that is called below	*/
/* only for 'in_sequence' packets, which is not the correct way to calc RTT	*/
/*--------------------------------------------------------------------------*/

	if	(conn->rtrp.mode && SEQ_diff(hdr->acknowledge,conn->rtrp.sequ) >= 0)
		do_RTT_calc(conn);

	if	(hdr->ack  &&  conn->ca.cwnd < MAX_WIND)
	{	if	(conn->ca.cwnd <= conn->ca.sstresh)			/* slow start ? */
			conn->ca.cwnd += conn->mss;
		else									/* congestion avoidance	*/
		{	if	(	conn->ca.time == 0L
				||	TIMER_elapsed(conn->ca.time) > conn->rtrp.srtt
				)
			{	conn->ca.time = TIMER_now();
				conn->ca.accu = 0;
			}
			if	(conn->ca.accu < conn->mss)
			{	uint16	aux = conn->mss*conn->mss/conn->ca.cwnd;
				if	(conn->ca.accu+aux > conn->mss)
					aux = conn->mss - conn->ca.accu;
				conn->ca.cwnd += aux;
				conn->ca.accu += aux;
			}
		}
	}

	if	(hdr->sequence != conn->recve.next && (net_data->data_len != 0 || hdr->sync || hdr->fin))
	{    add_resequ (conn, net_data);
		return;							/*----------------------------------*/
	}
/*--------------------------------------------------------------------------*/
/* At this stage of 'do_arrive' the data has been stored in segment buffer	*/
/* by 'add_resequ' after 'trim_segm' trimmed the data to match the window.	*/
/* The part below will attempt to merge the received data so as to form a	*/
/* contiguous buffer holding a maximum amount of it in a single NDB.		*/
/* This is done because the received packet had in-sequence data, and might	*/
/* cause an out-of-sequence buffer to become mergable. It is done in a loop	*/
/* to allow merging of multiple buffers, as time is not wasted on such work	*/
/* when out-of-sequence data is received, as that data might get scrapped	*/
/* due to duplication, in which case any work invested would become wasted.	*/
/*--------------------------------------------------------------------------*/
	for	(;;)
	{	hdr = net_data->hdr;
		len = (uint16) (net_data->data + net_data->data_len - (uint8 *) net_data->hdr);
		stored = FALSE;

		if (hdr->reset)
		{	if (conn->state == TSYN_RECV)
			{	/* RA: Just going back to TLISTEN is not enough !!!	*/
				/* RA: We must also restore initial address data.	*/
				conn->local_IP_address	= conn->open.sock.lhost;
				conn->local_port		= conn->open.sock.lport;
				conn->remote_IP_address	= conn->open.sock.rhost;
				conn->remote_port		= conn->open.sock.rport;
				conn->state = TLISTEN;			/* state transition !!! */
			}
			else
			{	close_self (conn, E_RRESET);
				conn->net_error = E_RRESET;
			}
			KRfree (net_data->hdr);
			KRfree (net_data);
			return;						/*----------------------------------*/
		}

		if (PREC (net_data->tos) != PREC (conn->tos) || hdr->sync)
		{	datagram->pkt_data   = hdr;
			datagram->pkt_length = len;
			KRfree (net_data);
			send_reset (datagram);
			conn->net_error = E_UA;
			my_conf.generic.stat_dropped++;
			return;						/*----------------------------------*/
		}

		if (! hdr->ack)
		{	KRfree (net_data->hdr);
			KRfree (net_data);
			my_conf.generic.stat_dropped++;
			return;						/*----------------------------------*/
		}

		switch (conn->state)
		{
		case TSYN_RECV :
			if (SEQ_in (hdr->acknowledge, conn->send.unack + 1, conn->send.next))
			{	update_wind (conn, hdr);
				conn->state = TESTABLISH;		/* state transition !!! */
			}
			else
			{	datagram->pkt_data   = hdr;
				datagram->pkt_length = len;
				KRfree (net_data);
				send_reset (datagram);
				conn->net_error = E_UA;
				my_conf.generic.stat_dropped++;
				return;					/*----------------------------------*/
			}
			break;
		case TESTABLISH :
		case TCLOSE_WAIT :
		case TFIN_WAIT2 :
			update_wind (conn, hdr);
			break;
		case TFIN_WAIT1 :
			update_wind (conn, hdr);
			if (conn->send.count == 0)
				conn->state = TFIN_WAIT2;		/* state transition !!! */
			break;
		case TCLOSING :
/*--------------------------------------------------------------------------*/
/* RA: This case should set up the final delay before going to TCLOSED when	*/
/* RA: the other TCP has had time to receive our sync.  The 'timer_work'	*/
/* RA: code for this seems quite OK, but it never is activated.  I think	*/
/* RA: this means that the code below is never reached, possibly because of	*/
/* RA: some 'return' earlier in 'do_arrive', but this needs more analysis.	*/
/* RA: Known facts:  'timer_work' is reached in TTIME_WAIT, but the other	*/
/* RA: conditions of the 'retrain' test for closing never match.			*/
/*--------------------------------------------------------------------------*/
			update_wind (conn, hdr);
			if	(conn->send.count == 0)			/* is our FIN ACKed ?	*/
			{	conn->state = TTIME_WAIT;		/* state transition !!! */
				conn->rtrn.mode    = TRUE;
				conn->rtrn.start   = TIMER_now();
				conn->rtrn.timeout = 2 * my_conf.max_slt;
			}
			break;
		case TLAST_ACK :
			update_wind (conn, hdr);
			if	(conn->send.count == 0)
			{	close_self (conn, E_NORMAL);
				KRfree (net_data->hdr);
				KRfree (net_data);
				return;					/*----------------------------------*/
			}
		case TTIME_WAIT :
			conn->flags |= FORCE;
			conn->rtrn.mode    = TRUE;
			conn->rtrn.start   = TIMER_now();
			conn->rtrn.timeout = 2 * my_conf.max_slt;
		}	/* ends: 2nd	switch(conn->state)	*/

		if (net_data->data_len != 0)
		{	switch (conn->state)
			{
			case TSYN_RECV :
			case TESTABLISH :
			case TFIN_WAIT1 :
			case TFIN_WAIT2 :
				temp = *net_data;
				ndb = (NDB *) net_data;
				ndb->ptr   = (char *) temp.hdr;
				ndb->ndata = temp.data;
				ndb->len   = temp.data_len;
				ndb->next  = NULL;
				if (conn->recve.queue)
				{	for (work = conn->recve.queue; work->next; work = work->next)
						;	/* NB: empty loop	*/
					work->next = ndb;
				}
				else
				{	conn->recve.queue = ndb;
				}
				conn->recve.count  += ndb->len;
				conn->recve.next += ndb->len;
				conn->recve.window -= ndb->len;
				conn->flags |= FORCE;
				stored = TRUE;
			}
		}	/* ends: 3rd	switch(conn->state)	*/

		if (hdr->fin)
		{	conn->flags |= FORCE;
			switch (conn->state)
			{
			case TSYN_RECV :
			case TESTABLISH :
				conn->recve.next++;
				conn->state = TCLOSE_WAIT;		/* state transition !!! */
				break;
			case TFIN_WAIT1 :
				if (conn->send.count != 0)
				{	conn->recve.next++;
					conn->state = TCLOSING;		/* state transition !!! */
					break;
				}
			case TFIN_WAIT2 :
				conn->recve.next++;
				conn->state = TTIME_WAIT;		/* state transition !!! */
				conn->rtrn.mode    = TRUE;
				conn->rtrn.start   = TIMER_now();
				conn->rtrn.timeout = 2 * my_conf.max_slt;
				break;
			case TCLOSE_WAIT :
			case TCLOSING :
			case TLAST_ACK :
				break;
			case TTIME_WAIT :
				conn->rtrn.mode    = TRUE;
				conn->rtrn.start   = TIMER_now();
				conn->rtrn.timeout = 2 * my_conf.max_slt;
				break;
			}	/* ends: 4th	switch(conn->state)	*/
		}

		if (! stored)
		{	KRfree (net_data->hdr);
			KRfree (net_data);
		}

		for (trim = FALSE; conn->recve.reseq != NULL;)
		{	if (SEQ_diff(conn->recve.next, conn->recve.reseq->hdr->sequence) < 0)
				break;
			net_data = conn->recve.reseq;
			conn->recve.reseq = net_data->next;
			if (trim_segm (conn, NULL, & net_data, FALSE))
			{	trim = TRUE;
				break;
			}
			KRfree (net_data->hdr);
			KRfree (net_data);
		}
		if (! trim)
			break;
	}	/* ends:	for loop	*/

	do_output (conn);
	return;
}
/*----------------------*/
/* ends:	do_arrive	*/
/*--------------------------------------------------------------------------*/
/* End of file:	ARRIVE.C													*/
/*--------------------------------------------------------------------------*/
