/*--------------------------------------------------------------------------*/
/*	File name:	INOUT.C							Revision date:	2000.08.14	*/
/*	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:		Input/Output functions.										*/
/*--------------------------------------------------------------------------*/
#include <tos.h>
#include <stdio.h>
#include <string.h>
#include <sting\transprt.h>
#include <sting\layer.h>
#include <sting\sys\tcp.h>
/*--------------------------------------------------------------------------*/
void	do_RTT_calc (CONNEC *conn);
void	update_wind (CONNEC *connec, TCP_HDR *tcph);
uint16	pull_up (NDB **queue, char *buffer, uint16 length);
uint16	peek_up (NDB *queue, char *buffer, uint16 length);
int16	trim_segm (CONNEC *connec, IP_DGRAM *dgram, RESEQU **block, int16 flag);
void	add_resequ (CONNEC *connec, RESEQU *block);
void	do_output (CONNEC *connec);
uint8	*prep_segment (CONNEC *connec, TCP_HDR *hdr, uint16 *length, uint16 offset, uint16 size);

extern	TCP_CONF  my_conf;

uint16	tcp_id = 0;
/*------------------------------*/
/* ends:	declaration header	*/
/*--------------------------------------------------------------------------*/
/* 'do_RTT_calc' below used to be incorporated with 'update_wind' further	*/
/* below, but this was changed to allow better control over the timing, and	*/
/* to avoid wasting time in a loop calling 'update_wind' in resequencing.	*/
/*--------------------------------------------------------------------------*/
void	do_RTT_calc (CONNEC *conn)
{	uint32	rtrip;

	conn->rtrp.mode = FALSE;
	if ((conn->flags & RETRAN) == 0)
	{	rtrip = TIMER_elapsed (conn->rtrp.start);
		conn->rtrp.srtt = ( 7 * conn->rtrp.srtt + rtrip) /  8;
		if	(conn->rtrp.srtt < 100L)
			conn->rtrp.rto	= 200L;			/* limit RTO to 200 ms minimum	*/
		else
			if	(conn->rtrp.srtt > 30000L)
				conn->rtrp.rto = 60000L;	/* limit RTO to 1 minute max	*/
			else
				conn->rtrp.rto = conn->rtrp.srtt<<1L;
		conn->rtrn.timeout = conn->rtrp.srtt<<1;
		conn->rtrn.start   = TIMER_now();
		conn->rtrn.backoff = 0;
	}
}
/*--------------------------*/
/* ends:	do_RTT_calc		*/
/*--------------------------------------------------------------------------*/
void  update_wind (CONNEC *connec, TCP_HDR *tcph)
{	uint32  rtrip, acked;

	if	(SEQ_diff(tcph->acknowledge, connec->send.next) > 0)
	{	connec->flags |= FORCE;
		return;
	}

	if	(	SEQ_diff(tcph->sequence, connec->send.lwup_seq) > 0
		||	(	(tcph->sequence == connec->send.lwup_seq)
			&&	(SEQ_diff(tcph->acknowledge, connec->send.lwup_ack) >= 0)
			)
		)
	{	if	(connec->send.window == 0 && tcph->window != 0)
			connec->send.ptr = connec->send.unack;
		connec->send.window   = tcph->window;
		connec->send.lwup_seq = tcph->sequence;
		connec->send.lwup_ack = tcph->acknowledge;
	}

	if	(SEQ_diff(tcph->acknowledge, connec->send.unack) <= 0)
		return;


	acked = SEQ_diff(tcph->acknowledge, connec->send.unack);	/* NB: >0	*/

	if	(	(connec->send.ini_sequ == connec->send.unack)
		&&	(connec->state == TSYN_SENT || connec->state == TSYN_RECV)
		)
	{	connec->send.count--;	/* remove SYN bit from send.count	*/
		acked--;				/* remove SYN bit from acked		*/
	}
	if	((connec->flags&FIN_QUEUED) && (connec->send.count == acked && acked))
	{	acked--;				/* remove FIN bit from 'acked'		*/
		connec->send.count--;	/* remove FIN bit from send.count	*/
	}
	pull_up (& connec->send.queue, NULL, acked);

	connec->send.count -= acked;
	connec->send.total -= acked;
	connec->send.unack  = tcph->acknowledge;

	if	(connec->send.unack != connec->send.next)
	{	connec->rtrn.mode    = TRUE;
		connec->rtrn.start   = TIMER_now();
		connec->rtrn.timeout = connec->rtrp.srtt<<1;
		connec->rtrn.backoff = 0;
	}
	else
		connec->rtrn.mode = FALSE;

	if	(SEQ_diff(connec->send.ptr, connec->send.unack) < 0)
		connec->send.ptr = connec->send.unack;

	connec->flags &= ~RETRAN;
}
/*--------------------------*/
/* ends:	update_wind		*/
/*--------------------------------------------------------------------------*/
uint16  pull_up (NDB **queue, char *buffer, uint16 length)
{	NDB     *temp;
	uint16  avail, accu = 0;

	while (length > 0 && *queue != NULL)
	{	avail = ((*queue)->len < length) ? (*queue)->len : length;
		if (buffer)
		{	memcpy (buffer, (*queue)->ndata, avail);
			buffer += avail;
		}
		(*queue)->len -= avail;
		(*queue)->ndata += avail;
		if ((*queue)->len == 0)
		{	KRfree ((*queue)->ptr);
			temp = *queue;
			*queue = (*queue)->next;
			KRfree (temp);
		}
		accu += avail;   length -= avail;
	}
	return (accu);
}
/*----------------------*/
/* ends:	pull_up		*/
/*--------------------------------------------------------------------------*/
uint16  peek_up (NDB *queue, char *buffer, uint16 length)
{	uint16  avail, accu = 0;

	while	(length > 0  &&  queue != NULL)
	{	avail = (queue->len < length) ? queue->len : length;
		if	(avail && buffer)
		{	memcpy (buffer, queue->ndata, avail);
			buffer += avail;
		}
		queue = queue->next;
		accu += avail;
		length -= avail;
	}
	return (accu);
}
/*----------------------*/
/* ends:	peek_up		*/
/*--------------------------------------------------------------------------*/
/* Function 'trim_segm' will check if a TCP datagram matches the expected	*/
/* sequence range. It will trim away any duplicate data from the data block	*/
/* and will also cut away any excess beyond the legal window limit.			*/
/* It returns FALSE if the sequence number range is not in a useful range	*
/* or if it fails to allocate RAM when this has been requested (make_f).	*/
/*--------------------------------------------------------------------------*/
int16	trim_segm(CONNEC *connec,IP_DGRAM *dgram,RESEQU **block,int16 make_f)
{	TCP_HDR  *hdr;
	uint32   wind_beg, wind_end;
	int32    dupes, excess;
	int16    dat_len, seq_len;
	uint8    *data;

	if	(make_f)
	{	if ((*block = KRmalloc (sizeof (RESEQU))) == NULL)
			return (FALSE);
		(*block)->tos       = dgram->hdr.tos;
		(*block)->hdr = hdr = (TCP_HDR *) dgram->pkt_data;
		(*block)->data      = (uint8 *) dgram->pkt_data + hdr->offset * 4;
		(*block)->data_len  = dgram->pkt_length - hdr->offset * 4;
	}

	hdr     = (*block)->hdr;
	data    = (*block)->data;
	dat_len = (*block)->data_len;

	seq_len = dat_len;

	if (hdr->sync)   seq_len++;
	if (hdr->fin)    seq_len++;

	wind_beg = connec->recve.next;
	wind_end = wind_beg + connec->recve.window - 1;

	if	(connec->recve.window == 0)
		if	(hdr->sequence == connec->recve.next && seq_len == 0)
			return (TRUE);		/* it's OK, but nothing to store	*/
		else
			return (FALSE);		/* erroneous sequence */

	if	(	(! SEQ_in (hdr->sequence, wind_beg, wind_end))
		&&	(! SEQ_in (hdr->sequence + seq_len - 1, wind_beg, wind_end))
		&&	(! SEQ_in (wind_beg, hdr->sequence, hdr->sequence + seq_len - 1))
		)
		return (FALSE);

	if	(hdr->sync)
	{	if	(connec->recve.next == hdr->sequence)
			connec->recve.next++;		/* swallow SYN */
		hdr->sync = FALSE;
		hdr->sequence++;
	}
	if ((dupes = connec->recve.next - hdr->sequence) > 0)
	{	data += dupes;
		dat_len -= dupes;
		hdr->sequence += dupes;
	}

	excess = (hdr->sequence + dat_len) - (connec->recve.next + connec->recve.window);

	if (excess > 0)
	{	if	(hdr->fin)
		{	excess--;
			hdr->fin = FALSE;
		}
		dat_len -= excess;
	}

	(*block)->data = data;
	(*block)->data_len = dat_len;

	return (TRUE);
}
/*----------------------*/
/* ends:	trim_segm	*/
/*--------------------------------------------------------------------------*/
void  add_resequ (CONNEC *connec, RESEQU *block)
{	RESEQU  *work;

	if	(connec->recve.reseq != NULL)
	{	if	(SEQ_diff(block->hdr->sequence, connec->recve.reseq->hdr->sequence) >= 0)
		{	for	(work = connec->recve.reseq; work->next; work = work->next)
			{	if	(SEQ_diff(block->hdr->sequence, work->next->hdr->sequence) < 0)
					break;
			}
			block->next = work->next;
			work->next = block;
			return;
		}
	}
	block->next = connec->recve.reseq;   connec->recve.reseq = block;
}
/*----------------------*/
/* ends:	add_resequ	*/
/*--------------------------------------------------------------------------*/
void  do_output (CONNEC *connec)
{	TCP_HDR	hdr;
	uint16	length, sent, usable_win, size;
	uint8	*block;
	int16	value, unforced, raw_size;
	uint16	mss = connec->mss;
	uint32	time_tmp;

/* RA: I'm editing many conditions in this function, to improve fluency */

	if	(	connec->state == TCLOSED
		||	connec->state == TLISTEN
		)
		return;

	for	(;;)
	{	unforced = ((connec->flags & FORCE) == 0);
		sent = connec->send.ptr - connec->send.unack;

		if	(connec->send.window <= sent)
		{	if	(sent != 0)
				return;
			usable_win = 1;
		}
		else
			usable_win = connec->send.window - sent;
		if	(usable_win > connec->ca.cwnd)
			usable_win = connec->ca.cwnd;

		size = connec->send.count - sent;	/* sequence length we want to send */

		if	(	(	unforced
				||	(	(time_tmp = TIMER_elapsed(connec->send.ACK_time)) < connec->rtrp.srtt>>1)
					&& 	(time_tmp < 500)
					&&  (connec->flags & (ACK_DELAYED | RETRAN) == 0)
				)
			&&	(	size == 0
				||	(	sent
					&&	(	size < mss
						||	usable_win < mss
						)
					)
				)
			)
		{	if	(!unforced)
				connec->flags |= ACK_DELAYED;
			return;		/* for these cases sending is deferred	*/
		}

		if	(size > mss)
			size = mss;
		if	(size > usable_win)
			size = usable_win;

		/* We will send packet, so remove FORCE flag */
		connec->flags &= ~FORCE;

		raw_size = size;

		hdr.urgent = hdr.push = hdr.reset = hdr.sync = hdr.fin = FALSE;
		hdr.ack = TRUE;
		hdr.sequence    = connec->send.ptr;
		hdr.acknowledge = connec->recve.next;

		if	(	connec->send.ptr == connec->send.ini_sequ
			&&	(connec->state == TSYN_SENT || connec->state == TSYN_RECV)
			)
		{	hdr.sync = TRUE;		/* insert SYN bit into TCP header	*/
			if	(connec->state == TSYN_SENT)
			{	hdr.ack = FALSE;						/* remove ACK bit	*/
				connec->rtrn.start   = TIMER_now();		/* restart timeout	*/
				connec->flags |= ACK_DELAYED;			/* forbid ACK delay	*/
			}
			if	(--raw_size < 0)	/* remove SYN bit from raw_size & test	*/
				raw_size = 0;		/* limit raw_size to positive values	*/ 
		}
		if	(hdr.ack)
		{	connec->flags &= ~ACK_DELAYED;
			connec->send.ACK_time = TIMER_now();
		}

		if	(	(connec->flags & FIN_QUEUED)			/* has APP closed ?	*/
			&&	(connec->send.count-sent == size)		/* and sending end	*/
			&&	connec->send.count						/* not yet ACKed ?	*/
			)
		{	hdr.fin = TRUE;			/* insert FIN bit into TCP header		*/
			if	(--raw_size < 0)	/* remove FIN bit from raw_size & test	*/
				raw_size = 0;		/* limit raw_size to positive values	*/
			if	(!size)			/* if resending unacked FIN alone (to ACK)	*/
				hdr.sequence = connec->send.unack-1;
		}

		if	(raw_size != 0 && sent + size == connec->send.count)
			hdr.push = TRUE;

		connec->send.ptr += size;

		if (SEQ_diff(connec->send.ptr, connec->send.next) > 0)
			connec->send.next = connec->send.ptr;

		if (size != 0)
		{	if (! connec->rtrp.mode)
			{	connec->rtrp.start = TIMER_now();
				connec->rtrp.mode  = TRUE;
				connec->rtrp.sequ  = connec->send.ptr;
			}
			if (! connec->rtrn.mode)
				connec->rtrn.mode  = TRUE;
		}
		else
		{	if (sent == 0)
				connec->rtrn.mode = FALSE;
		}

		if	((block = prep_segment (connec, & hdr, & length, sent, raw_size)) != NULL)
		{	value = IP_send (connec->local_IP_address, connec->remote_IP_address, connec->tos, 
					FALSE, connec->ttl, P_TCP, tcp_id++, block, length, NULL, 0);
			if	(value != E_NORMAL)
				KRfree (block);
			if	(value == E_UNREACHABLE)
				connec->net_error = E_UNREACHABLE;
		}
	}	/* ends: for_loop	*/
}
/*------------------*/
/* Ends: do_output	*/
/*--------------------------------------------------------------------------*/
uint8	*prep_segment(CONNEC *connec, TCP_HDR *hdr, uint16 *length, uint16 offset, uint16 size)
{	NDB      *work;
	uint32   chksum;
	uint16   *walk, chunk;
	uint8    *mem, *ptr;

	*length = sizeof (TCP_HDR) + ((hdr->sync) ? 4 : 0) + size;

	if	((mem = KRmalloc (*length)) == NULL)
        return (NULL);

	hdr->src_port  = connec->local_port;
	hdr->dest_port = connec->remote_port;
	hdr->offset    = (hdr->sync) ? 6 : 5;
	hdr->resvd     = 0;
	hdr->window    = connec->recve.window;
	hdr->urg_ptr   = 0;

	if	(hdr->sync)
	{	walk = (uint16 *) (mem + sizeof (TCP_HDR));
		*walk++ = 0x0204;
		*walk++ = connec->mss;
		if	(offset)
			offset--;	/* remove SYN bit from offset for stored data	*/
	}
	memcpy (mem, hdr, sizeof (TCP_HDR));
		
	if	(size > 0)
	{	ptr = mem + sizeof (TCP_HDR) + ((hdr->sync) ? 4 : 0);
		for (work = connec->send.queue; work != NULL; work = work->next)
		{	if	(work->len > offset)
				break;
			offset -= work->len;
		}
		for (; work != NULL && size > 0; work = work->next, offset = 0)
		{	chunk = (work->len - offset < size) ? work->len - offset : size;
			size -= chunk;
			memcpy (ptr, work->ndata + offset, chunk);
			ptr += chunk;
		}
	}
	((TCP_HDR *) mem)->chksum = 0;
	((TCP_HDR *) mem)->chksum =
		check_sum (connec->local_IP_address, connec->remote_IP_address, (TCP_HDR *) mem, *length);
	connec->recve.lst_win = connec->recve.window;
	connec->recve.lst_nxt = connec->recve.next;
	return (mem);
}
/*--------------------------*/
/* ends:	prep_segment	*/
/*--------------------------------------------------------------------------*/
/* End of file:	INOUT.C														*/
/*--------------------------------------------------------------------------*/
