/*--------------------------------------------------------------------------*/
/*	File name:	TOOL.C							Revision date:	2001.02.12	*/
/*	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:		Tool functions for various purposes							*/
/*--------------------------------------------------------------------------*/
/* RA: The code of *dup_close is now integrated into 'my_TCP_close', as		*/
/* RA: many things are needed for completion of close negotiation in both	*/
/* RA: full and half duplex cases.  (Interrupt driven by 'timer_work'.)		*/
/*--------------------------------------------------------------------------*/
#include <tos.h>
#include <stdio.h>
#include <sting\transprt.h>
#include <sting\layer.h>
#include <sting\sys\tcp.h>
/*--------------------------------------------------------------------------*/
void			do_arrive (CONNEC *conn, IP_DGRAM *dgram);

uint16			pull_up (NDB **queue, char *buffer, uint16 length);
uint16			peek_up (NDB *queue, char *buffer, uint16 length);
void			do_output (CONNEC *connec);

void	cdecl	timer_function (void);
int16			poll_receive (CONNEC *connec);
int16			timer_work (CONNEC *connec);
int16	cdecl	do_ICMP (IP_DGRAM *dgram);
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			send_abort (CONNEC *connec);
void			close_self (CONNEC *connec, int16 reason);
void			flush_queue (NDB **queue);
int32	cdecl	pe_unlink_conn(void *conn);	/* RA: new protect_exec func	*/
void			destroy_conn (CONNEC *connec);
int16			receive (CONNEC *connec, uint8 *buffer, int16 *length, int16 flag);
int16			discard (CONNEC *connec);
int16			categorize (CONNEC *connec);


extern	TCP_CONF	my_conf;
extern	CONNEC		*root_list;
extern	uint16		tcp_id;

uint32	ini_sequ;		/* initial sequence number, clock_based + offset	*/
uint32	ini_sequ_offs;	/* ini_sequ offset, increased for each connection	*/
CONNEC	*global;
int16	global_sema = 0;
/*--------------------------------------------------------------------------*/
void	cdecl  timer_function()

{	CONNEC  *connect, *next;

	for (connect = root_list; connect; connect = next)
	{	next = connect->next;
		timer_work (connect);
	}
}
/*--------------------------*/
/* ends:	timer_function	*/
/*--------------------------------------------------------------------------*/
int16	poll_receive (CONNEC *connec)

{	int16	error = connec->net_error;

	if	(error < 0)
	{	connec->net_error = E_NORMAL;
		return (error);
	}

	if (TIMER_elapsed (connec->last_work) < 1200)
		return(0);

	return (protect_exec((void *) connec, pe_poll_work) ? E_NORMAL : E_NOCONNECTION);
}
/*--------------------------*/
/* ends:	poll_receive	*/
/*--------------------------------------------------------------------------*/
int16	timer_work (CONNEC *connec)

{	IP_DGRAM	*walk, *next;
	uint32		time_tmp;

	if	(TRY_lock (connec))		/* Is connection already locked ?	*/
		return (TRUE);			/* Ooops ! It was, we must return !	*/

	if (connec->pending)
	{	for (walk = get_pending (& connec->pending); walk; walk = next)
		{	next = walk->next;
			do_arrive (connec, walk);
			IP_discard (walk, TRUE);
		}
	}

	if	(connec->rtrn.mode)
	{	if	(TIMER_elapsed (connec->rtrn.start) > connec->rtrn.timeout)
		{	connec->rtrn.mode = FALSE;
			connec->rtrp.mode = FALSE;	/* break RTT measurement on timeout */
			if	(connec->state != TTIME_WAIT)
			{	connec->send.ptr = connec->send.unack;
				connec->flags   |= RETRAN;
				connec->rtrn.start   = TIMER_now();
				connec->rtrn.timeout = RTO_calc(connec) << (++connec->rtrn.backoff);
				if	(connec->send.window<=connec->ca.cwnd)
					connec->ca.sstresh = connec->send.window/2;
				else
					connec->ca.sstresh = connec->ca.cwnd/2;
				if	(connec->ca.sstresh < connec->mss)
					connec->ca.sstresh = connec->mss;
				connec->ca.cwnd = connec->mss;	/* congestion avoidance window	*/
				do_output (connec);
			}
			else	/* connec->state == TTIME_WAIT	*/
				close_self (connec, E_NORMAL);
		}
	}

	if	(	(connec->flags & ACK_DELAYED)
		&&	(connec->flags & FORCE)
		&&	(	((time_tmp = TIMER_elapsed(connec->send.ACK_time)) >= 500)
			||	(time_tmp >= (connec->rtrp.srtt >> 1))
			)
		)
	{	do_output (connec);
	}

/* RA: The conditionals below have been extensively changed so as to allow	*/
/* RA: safe closing and final destruction of connections after the closing	*/
/* RA: negotiation completes or times out under interrupt driven control.	*/

	if	(connec->flags & CLOSING)				/* TCP_close called yet ?	*/
	{	if	(connec->state == TCLOSED)			/* TCLOSED achieved yet ?	*/
		{
Destroy_conn_now:
			destroy_conn(connec);				/* Release connection RAM	*/
			return (FALSE);						/* Return 'no_conn' flag	*/
		}

		if	(connec->state == TTIME_WAIT)
		{	if	(TIMER_elapsed (connec->send.ACK_time) > 2*my_conf.max_slt)
			{	close_self(connec, E_NORMAL);		/* close without error		*/
				goto	Destroy_conn_now;			/* go release conn & exit	*/
			}
			else
				if (connec->result)
				{	*connec->result = E_NORMAL;
					connec->result = NULL;
				}
		}

		if	(TIMER_elapsed (connec->close.start) > connec->close.timeout)
		{	send_abort (connec);
			close_self (connec, E_CNTIMEOUT);
			goto	Destroy_conn_now;			/* go release conn & exit	*/
		}

		if	(connec->net_error != 0)
		{	close_self (connec, E_CNTIMEOUT);
			goto	Destroy_conn_now;			/* go release conn & exit	*/
		}
	}
	connec->last_work = TIMER_now();
	END_lock(connec);
	return (TRUE);
}
/*----------------------*/
/* ends:	timer_work	*/
/*--------------------------------------------------------------------------*/
int16  cdecl  do_ICMP (IP_DGRAM *dgram)

{	IP_HDR   *ip;
	TCP_HDR  *tcp;
	CONNEC   *connect;
	int16    count;
	uint8    type, code;

	if ((my_conf.generic.flags & 0x10000ul) == 0)
		return (FALSE);

	type = *  (uint8 *) dgram->pkt_data;
	code = * ((uint8 *) dgram->pkt_data + 1);

	if (type != 3 && type != 4 && type != 11)
		return (FALSE);

	ip = (IP_HDR *) ((uint8 *) dgram->pkt_data + 8);

	if (ip->protocol != P_TCP)
		return (FALSE);

	tcp = (TCP_HDR *) ((uint8 *) ip + ip->hd_len * 4);

	for (connect = root_list; connect; connect = connect->next)
	{	if (tcp->src_port  != connect->local_port)
			continue;
		if (tcp->dest_port != connect->remote_port)
			continue;
		if (ip->ip_src  != connect->local_IP_address)
			continue;
		if (ip->ip_dest != connect->remote_IP_address)
			continue;
		break;
	}

	if (connect == NULL)
	{	ICMP_discard (dgram);
		return (TRUE);
	}

	if (! SEQ_in (tcp->sequence, connect->send.unack, connect->send.next))
	{	ICMP_discard (dgram);
		return (TRUE);
	}

	if (connect->info)
		connect->info->status = (uint16) type << 8 | code;

	if (connect->state == TSYN_SENT || connect->state == TSYN_RECV)
	{	connect->net_error = E_CONNECTFAIL;
		close_self (connect, connect->net_error);
	}
	else
	{	switch (type)
		{
		case  3 :   connect->net_error = E_UNREACHABLE;   break;
		case  4 :   connect->net_error = E_CNTIMEOUT;     break;
		case 11 :   connect->net_error = E_TTLEXCEED;     break;
		}
	}
	ICMP_discard (dgram);
	return (TRUE);
}
/*----------------------*/
/* ends:	do_ICMP		*/
/*--------------------------------------------------------------------------*/
void  send_sync (CONNEC *connec)

{	ini_sequ_offs += 250052L;
	ini_sequ = (uint32)(TIMER_now()<<8L) + ini_sequ_offs;
	connec->send.ini_sequ = ini_sequ;
	connec->rtrp.sequ = connec->send.lwup_ack = connec->send.unack = connec->send.ini_sequ;
	connec->send.ptr  = connec->send.next     = connec->send.ini_sequ;
	connec->send.count++;			/* count SYN bit in send.count	*/
	connec->flags |= FORCE;
}
/*----------------------*/
/* ends:	send_sync	*/
/*--------------------------------------------------------------------------*/
void	process_sync (CONNEC *connec, IP_DGRAM *dgram)

{	uint16  max_mss;

	connec->flags |= FORCE;		/* force undelayed reply packet */

	if (PREC_bits(dgram->hdr.tos) > PREC_bits(connec->tos))
		connec->tos = dgram->hdr.tos;
	else
		connec->tos = PREC_bits(connec->tos)|un_PREC_bits(dgram->hdr.tos);
		
	connec->send.lwup_seq = ((TCP_HDR *) dgram->pkt_data)->sequence;
	connec->send.window   = ((TCP_HDR *) dgram->pkt_data)->window;
	connec->recve.next    = ((TCP_HDR *) dgram->pkt_data)->sequence;

	process_options (connec, dgram);

	max_mss = connec->mtu - sizeof (IP_HDR) - sizeof (TCP_HDR);
	if (connec->mss > max_mss)
		connec->mss = max_mss;
	connec->ca.cwnd = connec->mss*2;
	connec->ca.sstresh = MAX_WIND;
}
/*--------------------------*/
/* ends:	process_sync	*/
/*--------------------------------------------------------------------------*/
void  process_options (CONNEC *connec, IP_DGRAM *dgram)

{	uint8   *work, *limit;
	uint16  new_mss;

	work = (uint8 *) ((TCP_HDR *) dgram->pkt_data + 1);
	limit = (uint8 *) dgram->pkt_data + ((TCP_HDR *) dgram->pkt_data)->offset * 4;

	while (limit > work)
	{	switch (*work)
		{
		case 0 :				/* End of option list	*/
			work = limit;		/* so skip the rest.	*/
			break;
		case 1 :				/* No-operation			*/
			work++;				/* so just ignore it	*/
			break;
		case 2 :				/* Maximum Segment size	*/
			new_mss = ((uint16) work[2] << 8) | ((uint16) work[3]);
			connec->mss = (new_mss < connec->mss) ? new_mss : connec->mss;
			work += work[1];
			break;
		default :				/* Undefined option		*/
			work += work[1];	/* so just ignore it	*/
		}
	}
}
/*------------------------------*/
/* ends:	process_options		*/
/*--------------------------------------------------------------------------*/
void	send_reset (IP_DGRAM *dgram)

{	TCP_HDR  *hdr;
	uint16   ports;

	if ((hdr = (TCP_HDR *) dgram->pkt_data)->reset)
		return;			/* never reset to reply to incoming reset */

	my_conf.resets++;

	ports = hdr->src_port;
	hdr->src_port = hdr->dest_port;
	hdr->dest_port = ports;

	if (hdr->ack)
	{	hdr->sequence = hdr->acknowledge;
		hdr->acknowledge = 0;
		hdr->ack = hdr->sync = hdr->push = hdr->urgent = FALSE;
	}
	else
	{	hdr->ack = TRUE;
		hdr->acknowledge = hdr->sequence + dgram->pkt_length - hdr->offset * 4;
		hdr->sequence = 0;
		if (hdr->sync)   hdr->acknowledge++;
		if (hdr->fin)    hdr->acknowledge++;
		hdr->sync = hdr->push = hdr->urgent = FALSE;
	}
	hdr->reset = TRUE;

	hdr->window = hdr->chksum = hdr->urg_ptr = 0;
	hdr->offset = 5;		/* NB: Bugfix, this used to be 0 (above) */

	hdr->chksum =
		check_sum (dgram->hdr.ip_dest, dgram->hdr.ip_src, hdr, sizeof (TCP_HDR));

	IP_send (dgram->hdr.ip_dest, dgram->hdr.ip_src, dgram->hdr.tos, FALSE, 
		my_conf.def_ttl, P_TCP, tcp_id++, (uint8 *) hdr, sizeof (TCP_HDR), NULL, 0);

	dgram->pkt_data = NULL;
}
/*----------------------*/
/* Ends:	send_reset	*/
/*--------------------------------------------------------------------------*/
void  send_abort (CONNEC *connec)

{	TCP_HDR  *hdr;

	if ((hdr = (TCP_HDR *) KRmalloc (sizeof (TCP_HDR))) == NULL)
		return;

	my_conf.resets++;

	hdr->src_port  = connec->local_port;
	hdr->dest_port = connec->remote_port;

	hdr->sequence = connec->send.next;
	hdr->acknowledge = 0;

	hdr->urgent = hdr->ack = hdr->push = hdr->sync = hdr->fin = FALSE;
	hdr->reset = TRUE;

	hdr->offset = hdr->resvd = hdr->window = hdr->chksum = hdr->urg_ptr = 0;

	hdr->chksum = 
		check_sum (connec->local_IP_address, connec->remote_IP_address, hdr, sizeof (TCP_HDR));

	IP_send (connec->local_IP_address, connec->remote_IP_address, connec->tos, FALSE, 
			connec->ttl, P_TCP, tcp_id++, (uint8 *) hdr, sizeof (TCP_HDR), NULL, 0);
}
/*----------------------*/
/* ends:	send_abort	*/
/*--------------------------------------------------------------------------*/
void	close_self (CONNEC *connec, int16 reason)

{	RESEQU  *work, *temp;
	int16	old_SR = (int16) pe_dis_imask(NULL);	/* block interrupts	*/

	connec->rtrn.mode = FALSE;
	connec->rtrp.mode = FALSE;
	connec->reason = reason;
	work = connec->recve.reseq;		/* to release resequence buffers below	*/
	connec->recve.reseq = NULL;
	connec->state = TCLOSED;					/* state transition !!! */

	if (connec->result)
	{	if (connec->send.count == 0 && reason == E_NORMAL)
			*connec->result = E_NORMAL;
		else
			*connec->result = (reason == E_NORMAL) ? E_EOF : reason;
	}
	(void) pe_set_imask((void *) old_SR);		/* release interrupts	*/

	while	( work )
	{	temp = work->next;
		KRfree (work->hdr);
		KRfree (work);
		work = temp;
	}
}
/*----------------------*/
/* ends:	close_self	*/
/*--------------------------------------------------------------------------*/
void  flush_queue (NDB **queue)

{	NDB	*walk, *temp;
	if	(*queue == NULL)
		return;

	walk = *queue;
	*queue = NULL;

	while	( walk )
	{	temp = walk->next;
		KRfree (walk->ptr);
		KRfree (walk);
		walk = temp;
	}
}
/*--------------------------*/
/* ends:	flush_queue		*/
/*--------------------------------------------------------------------------*/
int32	cdecl	pe_unlink_conn(void *conn)

{	CONNEC    *work, **previous;

	for (work = * (previous = & root_list); work; work = * (previous = & work->next))
	{	if (work == (CONNEC *) conn)
			break;
	}
	if (work)
		*previous = work->next;
	return	((int32) previous);
}
/*--------------------------*/
/* ends:	pe_unlink_conn	*/
/*--------------------------------------------------------------------------*/
void	destroy_conn (CONNEC *connec)

{	CONNEC    *work, **previous;
	IP_DGRAM  *ip_walk, *ip_next;
	RESEQU    *rsq_walk, *rsq_next;

	protect_exec(connec, pe_unlink_conn);	/* RA: code moved to subfunc	*/

	flush_queue (& connec->send.queue);
	flush_queue (& connec->recve.queue);

	for (ip_walk = connec->pending; ip_walk; ip_walk = ip_next)
	{	ip_next = ip_walk->next;
		IP_discard (ip_walk, TRUE);
	}
	for (rsq_walk = connec->recve.reseq; rsq_walk; rsq_walk = rsq_next)
	{	rsq_next = rsq_walk->next;
		KRfree (rsq_walk->hdr);   KRfree (rsq_walk);
	}
/* RA: PRTCL_release has been moved here, so it can be performed in a safe	*/
/* RA: and fully consistent way for all closed connection.  It really does	*/
/* RA: belong here, together with the code that releases the RAM blocks.	*/
	PRTCL_release(connec->open.handle);
	KRfree (connec->info);
	KRfree (connec);
}
/*--------------------------*/
/* ends:	destroy_conn	*/
/*--------------------------------------------------------------------------*/
/* 'receive' below may only be called with connec->sema already locked		*/
/*--------------------------------------------------------------------------*/
int16	receive (CONNEC *connec, uint8 *buffer, int16 *length, int16 peek_f)

{	NDB  *ndb;

	if	(*length >= 0)
	{	if	(*length > connec->recve.count)
			*length = connec->recve.count;
		if	(peek_f)
		{	peek_up (connec->recve.queue, buffer, *length);
			return (connec->recve.count);
		}
		else
			pull_up (& connec->recve.queue, buffer, *length);
	}
	else
	{	if	((ndb = connec->recve.queue) == NULL)
			*length = -1;
		else
		{	*length = ndb->len;
			* (NDB **) buffer = ndb;
			connec->recve.queue = ndb->next;
		}
	}

	if	(*length > 0)
	{	connec->recve.count  -= *length;
		connec->recve.window += *length;

		if	(	connec->recve.window == *length
			||	(	connec->recve.lst_win < connec->mss
				&&	connec->recve.window >= connec->mss
				)
			||  SEQ_diff
				(	connec->recve.next + connec->recve.window
				,	connec->recve.lst_nxt + connec->recve.lst_win
				) >= connec->mss
			)
		{	connec->flags |= FORCE;
			do_output (connec);
		}
	}
	return (connec->recve.count);
}
/*----------------------*/
/* ends:	receive		*/
/*--------------------------------------------------------------------------*/
int16  discard (CONNEC *connec)

{	NDB  *ndb;

	if ((ndb = connec->recve.queue) == NULL)
		return (TRUE);

	connec->recve.queue   = ndb->next;
	connec->recve.count  -= ndb->len;
	connec->recve.window += ndb->len;

	connec->flags |= FORCE;
	do_output (connec);

	KRfree (ndb->ptr);   KRfree (ndb);

	return (FALSE);
}
/*----------------------*/
/* ends:	discard		*/
/*--------------------------------------------------------------------------*/
int16  categorize (CONNEC *connec)

{	switch (connec->state)
	{
	case TLISTEN :
		return (C_LISTEN);
	case TSYN_SENT :
	case TSYN_RECV :
	case TESTABLISH :
		return (C_READY);
	case TFIN_WAIT1 :
	case TFIN_WAIT2 :
		return (C_FIN);
	case TCLOSE_WAIT :
		return ((connec->recve.count) ? C_READY : C_END);
	case TCLOSED :
	case TCLOSING :
	case TLAST_ACK :
	case TTIME_WAIT :
		return ((connec->recve.count) ? C_FIN : C_CLSD);
	}
	return (C_DEFAULT);
}
/*----------------------*/
/* ends:	categorize	*/
/*--------------------------------------------------------------------------*/
/* End of file:	TOOL.C														*/
/*--------------------------------------------------------------------------*/
