/*
tcp_recv.c

Copyright 1995 Philip Homburg
*/

#include "inet.h"
#include "buf.h"
#include "clock.h"
#include "event.h"
#include "type.h"

#include "io.h"
#include "tcp_int.h"
#include "tcp.h"
#include "assert.h"

THIS_FILE

FORWARD void create_RST ARGS(( tcp_conn_t *tcp_conn,
	ip_hdr_t *ip_hdr, tcp_hdr_t *tcp_hdr, int data_len ));
FORWARD void process_data ARGS(( tcp_conn_t *tcp_conn,
	tcp_hdr_t *tcp_hdr, acc_t *tcp_data, int data_len ));
FORWARD void process_advanced_data ARGS(( tcp_conn_t *tcp_conn,
	tcp_hdr_t *tcp_hdr, acc_t *tcp_data, int data_len ));

PUBLIC void tcp_frag2conn(tcp_conn, ip_hdr, tcp_hdr, tcp_data, data_len)
tcp_conn_t *tcp_conn;
ip_hdr_t *ip_hdr;
tcp_hdr_t *tcp_hdr;
acc_t *tcp_data;
size_t data_len;
{
	tcp_fd_t *connuser;
	int tcp_hdr_flags;
	int ip_hdr_len, tcp_hdr_len;
	u32_t seg_ack, seg_seq, rcv_hi;
	u16_t seg_wnd;
	int acceptable_ACK, segm_acceptable;

	ip_hdr_len= (ip_hdr->ih_vers_ihl & IH_IHL_MASK) << 2;
	tcp_hdr_len= (tcp_hdr->th_data_off & TH_DO_MASK) >> 2;

	tcp_hdr_flags= tcp_hdr->th_flags & TH_FLAGS_MASK;
	seg_ack= ntohl(tcp_hdr->th_ack_nr);
	seg_seq= ntohl(tcp_hdr->th_seq_nr);
	seg_wnd= ntohs(tcp_hdr->th_window);

	switch (tcp_conn->tc_state)
	{
	case TCS_CLOSED:
/*
CLOSED:
	discard all data.
	!RST ?
		ACK ?
			<SEQ=SEG.ACK><CTL=RST>
			exit
		:
			<SEQ=0><ACK=SEG.SEQ+SEG.LEN><CTL=RST,ACK>
			exit
	:
		discard packet
		exit
*/

		if (!(tcp_hdr_flags & THF_RST))
		{
			create_RST(tcp_conn, ip_hdr, tcp_hdr, data_len);
			tcp_conn_write(tcp_conn, 1);
		}
		break;
	case TCS_LISTEN:
/*
LISTEN:
	RST ?
		discard packet
		exit
	ACK ?
		<SEQ=SEG.ACK><CTL=RST>
		exit
	SYN ?
		BUG: no security check
		RCV.NXT= SEG.SEQ+1
		IRS= SEG.SEQ
		ISS should already be selected
		<SEQ=ISS><ACK=RCV.NXT><CTL=SYN,ACK>
		SND.NXT=ISS+1
		SND.UNA=ISS
		state= SYN-RECEIVED
		exit
	:
		shouldnot occur
		discard packet
		exit
*/
		if (tcp_hdr_flags & THF_RST)
			break;
		if (tcp_hdr_flags & THF_ACK)
		{
			create_RST (tcp_conn, ip_hdr, tcp_hdr, data_len);
			tcp_conn_write(tcp_conn, 1);
			break;
		}
		if (tcp_hdr_flags & THF_SYN)
		{
			tcp_extract_ipopt(tcp_conn, ip_hdr);
			tcp_extract_tcpopt(tcp_conn, tcp_hdr);
			tcp_conn->tc_RCV_LO= seg_seq+1;
			tcp_conn->tc_RCV_NXT= seg_seq+1;
			tcp_conn->tc_RCV_HI= tcp_conn->tc_RCV_LO+
				tcp_conn->tc_rcv_wnd;
			tcp_conn->tc_RCV_UP= seg_seq;
			tcp_conn->tc_IRS= seg_seq;
			tcp_conn->tc_SND_UNA= tcp_conn->tc_ISS;
			tcp_conn->tc_SND_TRM= tcp_conn->tc_ISS;
			tcp_conn->tc_SND_NXT= tcp_conn->tc_ISS+1;
			tcp_conn->tc_SND_UP= tcp_conn->tc_ISS-1;
			tcp_conn->tc_SND_PSH= tcp_conn->tc_ISS-1;
			tcp_conn->tc_state= TCS_SYN_RECEIVED;
			tcp_conn->tc_stt= 0;
			assert (tcp_check_conn(tcp_conn));
			tcp_conn->tc_locaddr= ip_hdr->ih_dst;
			tcp_conn->tc_locport= tcp_hdr->th_dstport;
			tcp_conn->tc_remaddr= ip_hdr->ih_src;
			tcp_conn->tc_remport= tcp_hdr->th_srcport;
			tcp_conn_write(tcp_conn, 1);

			DIFBLOCK(0x10, seg_seq == 0,
				printf("warning got 0 IRS from ");
				writeIpAddr(tcp_conn->tc_remaddr);
				printf("\n"));

			/* Start the timer (if necessary) */
			tcp_set_send_timer(tcp_conn);

			break;
		}
		/* do nothing */
		break;
	case TCS_SYN_SENT:
/*
SYN-SENT:
	ACK ?
		SEG.ACK <= ISS || SEG.ACK > SND.NXT ?
			RST ?
				discard packet
				exit
			:
				<SEQ=SEG.ACK><CTL=RST>
				exit
		SND.UNA <= SEG.ACK && SEG.ACK <= SND.NXT ?
			ACK is acceptable
		:
			ACK is !acceptable
	:
		ACK is !acceptable
	RST ?
		ACK acceptable ?
			discard segment
			state= CLOSED
			error "connection refused"
			exit
		:
			discard packet
			exit
	BUG: no security check
	SYN ?
		IRS= SEG.SEQ
		RCV.NXT= IRS+1
		ACK ?
			SND.UNA= SEG.ACK
		SND.UNA > ISS ?
			state= ESTABLISHED
			<SEQ=SND.NXT><ACK= RCV.NXT><CTL=ACK>
			process ev. URG and text
			exit
		:
			state= SYN-RECEIVED
			SND.WND= SEG.WND
			SND.WL1= SEG.SEQ
			SND.WL2= SEG.ACK
			<SEQ=ISS><ACK=RCV.NXT><CTL=SYN,ACK>
			exit
	:
		discard segment
		exit
*/
		if (tcp_hdr_flags & THF_ACK)
		{
			if (tcp_LEmod4G(seg_ack, tcp_conn->tc_ISS) ||
				tcp_Gmod4G(seg_ack, tcp_conn->tc_SND_NXT))
				if (tcp_hdr_flags & THF_RST)
					break;
				else
				{
					create_RST (tcp_conn, ip_hdr,
						tcp_hdr, data_len);
					tcp_conn_write(tcp_conn, 1);
					break;
				}
			acceptable_ACK= (tcp_LEmod4G(tcp_conn->tc_SND_UNA,
				seg_ack) && tcp_LEmod4G(seg_ack,
				tcp_conn->tc_SND_NXT));
		}
		else
			acceptable_ACK= FALSE;
		if (tcp_hdr_flags & THF_RST)
		{
			if (acceptable_ACK)
			{
				DBLOCK(1, printf(
					"calling tcp_close_connection\n"));

				tcp_close_connection(tcp_conn,
					ECONNREFUSED);
			}
			break;
		}
		if (tcp_hdr_flags & THF_SYN)
		{
			tcp_conn->tc_RCV_LO= seg_seq+1;
			tcp_conn->tc_RCV_NXT= seg_seq+1;
			tcp_conn->tc_RCV_HI= tcp_conn->tc_RCV_LO +
				tcp_conn->tc_rcv_wnd;
			tcp_conn->tc_RCV_UP= seg_seq;
			tcp_conn->tc_IRS= seg_seq;
			if (tcp_hdr_flags & THF_ACK)
				tcp_conn->tc_SND_UNA= seg_ack;
			if (tcp_Gmod4G(tcp_conn->tc_SND_UNA,
				tcp_conn->tc_ISS))
			{
				tcp_conn->tc_state= TCS_ESTABLISHED;
				tcp_conn->tc_rt_dead= TCP_DEF_RT_DEAD;

				assert (tcp_check_conn(tcp_conn));
				assert(tcp_conn->tc_connInprogress);

				tcp_restart_connect(tcp_conn->tc_fd);

				tcp_conn->tc_flags |= TCF_SEND_ACK;
				tcp_conn_write(tcp_conn, 1);
				if (data_len != 0)
				{
					tcp_frag2conn(tcp_conn, ip_hdr,
						tcp_hdr, tcp_data, data_len);
					/* tcp_data is already freed */
					return;
				}
				break;
			}
			tcp_conn->tc_state= TCS_SYN_RECEIVED;

			assert (tcp_check_conn(tcp_conn));

			tcp_conn->tc_SND_TRM= tcp_conn->tc_ISS;
			tcp_conn_write(tcp_conn, 1);
		}
		break;

	case TCS_SYN_RECEIVED:
/*
SYN-RECEIVED:
	test if segment is acceptable:
	Segment	Receive	Test
	Length	Window
	0	0	SEG.SEQ == RCV.NXT
	0	>0	RCV.NXT <= SEG.SEQ && SEG.SEQ < RCV.NXT+RCV.WND
	>0	0	not acceptable
	>0	>0	(RCV.NXT <= SEG.SEQ && SEG.SEQ < RCV.NXT+RCV.WND)
			|| (RCV.NXT <= SEG.SEQ+SEG.LEN-1 &&
			SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND)
	for urgent data: use RCV.WND+1 for RCV.WND
*/
		rcv_hi= tcp_conn->tc_RCV_HI;
		if (tcp_hdr_flags & THF_URG)
			rcv_hi++;
		if (!data_len)
		{
			if (rcv_hi == tcp_conn->tc_RCV_NXT)
				segm_acceptable= (seg_seq == rcv_hi);
			else
			{
				assert (tcp_Gmod4G(rcv_hi,
					tcp_conn->tc_RCV_NXT));
				segm_acceptable= (tcp_LEmod4G(tcp_conn->
					tc_RCV_NXT, seg_seq) &&
					tcp_Lmod4G(seg_seq, rcv_hi));
			}
		}
		else
		{
			if (tcp_Gmod4G(rcv_hi, tcp_conn->tc_RCV_NXT))
			{
				segm_acceptable= (tcp_LEmod4G(tcp_conn->
					tc_RCV_NXT, seg_seq) &&
					tcp_Lmod4G(seg_seq, rcv_hi)) ||
					(tcp_LEmod4G(tcp_conn->tc_RCV_NXT,
					seg_seq+data_len-1) &&
					tcp_Lmod4G(seg_seq+data_len-1,
					rcv_hi));
			}
			else
			{
				segm_acceptable= FALSE;
			}
		}
/*
	!segment acceptable ?
		RST ?
			discard packet
			exit
		:
			<SEG=SND.NXT><ACK=RCV.NXT><CTL=ACK>
			exit
*/
		if (!segm_acceptable)
		{
			if (!(tcp_hdr_flags & THF_RST))
			{
				tcp_conn->tc_flags |= TCF_SEND_ACK;
				tcp_conn_write(tcp_conn, 1);
			}
			break;
		}
/*
	RST ?
		initiated by a LISTEN ?
			state= LISTEN
			exit
		:
			state= CLOSED
			error "connection refused"
			exit
*/
		if (tcp_hdr_flags & THF_RST)
		{
			if (tcp_conn->tc_orglisten)
			{
				connuser= tcp_conn->tc_fd;

				tcp_conn->tc_connInprogress= 0;
				tcp_conn->tc_fd= NULL;

				tcp_close_connection (tcp_conn, ECONNREFUSED);
				if (connuser)
					(void)tcp_su4listen(connuser);
				break;
			}
			else
			{
				tcp_close_connection(tcp_conn, ECONNREFUSED);
				break;
			}
		}
/*
	SYN in window ?
		initiated by a LISTEN ?
			state= LISTEN
			exit
		:
			state= CLOSED
			error "connection reset"
			exit
*/
		if ((tcp_hdr_flags & THF_SYN) && tcp_GEmod4G(seg_seq,
			tcp_conn->tc_RCV_NXT))
		{
			if (tcp_conn->tc_orglisten)
			{
				connuser= tcp_conn->tc_fd;

				tcp_conn->tc_connInprogress= 0;
				tcp_conn->tc_fd= NULL;

				tcp_close_connection(tcp_conn, ECONNRESET);
				if (connuser)
					(void)tcp_su4listen(connuser);
				break;
			}
			tcp_close_connection(tcp_conn, ECONNRESET);
			break;
		}
/*
	!ACK ?
		discard packet
		exit
*/
		if (!(tcp_hdr_flags & THF_ACK))
			break;
/*
	SND.UNA < SEG.ACK <= SND.NXT ?
		state= ESTABLISHED
	:
		<SEG=SEG.ACK><CTL=RST>
		exit
*/
		if (tcp_Lmod4G(tcp_conn->tc_SND_UNA, seg_ack) &&
			tcp_LEmod4G(seg_ack, tcp_conn->tc_SND_NXT))
		{
			tcp_conn->tc_state= TCS_ESTABLISHED;
			tcp_conn->tc_rt_dead= TCP_DEF_RT_DEAD;

			tcp_release_retrans(tcp_conn, seg_ack, seg_wnd);

			assert (tcp_check_conn(tcp_conn));
			assert(tcp_conn->tc_connInprogress);

			tcp_restart_connect(tcp_conn->tc_fd);
			tcp_frag2conn(tcp_conn, ip_hdr, tcp_hdr, tcp_data,
				data_len);
			/* tcp_data is already freed */
			return;
		}
		else
		{
			create_RST (tcp_conn, ip_hdr, tcp_hdr, data_len);
			tcp_conn_write(tcp_conn, 1);
			break;
		}
		break;

	case TCS_ESTABLISHED:
	case TCS_CLOSING:
/*
ESTABLISHED:
FIN-WAIT-1:
FIN-WAIT-2:
CLOSE-WAIT:
CLOSING:
LAST-ACK:
TIME-WAIT:
	test if segment is acceptable:
	Segment	Receive	Test
	Length	Window
	0	0	SEG.SEQ == RCV.NXT
	0	>0	RCV.NXT <= SEG.SEQ && SEG.SEQ < RCV.NXT+RCV.WND
	>0	0	not acceptable
	>0	>0	(RCV.NXT <= SEG.SEQ && SEG.SEQ < RCV.NXT+RCV.WND)
			|| (RCV.NXT <= SEG.SEQ+SEG.LEN-1 &&
			SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND)
	for urgent data: use RCV.WND+1 for RCV.WND
*/
		rcv_hi= tcp_conn->tc_RCV_HI;
		if (tcp_hdr_flags & THF_URG)
			rcv_hi++;
		if (!data_len)
		{
			if (rcv_hi == tcp_conn->tc_RCV_NXT)
				segm_acceptable= (seg_seq == rcv_hi);
			else
			{
				assert (tcp_Gmod4G(rcv_hi,
					tcp_conn->tc_RCV_NXT));
				segm_acceptable= (tcp_LEmod4G(tcp_conn->
					tc_RCV_NXT, seg_seq) &&
					tcp_Lmod4G(seg_seq, rcv_hi));
			}
		}
		else
		{
			if (tcp_Gmod4G(rcv_hi, tcp_conn->tc_RCV_NXT))
			{
				segm_acceptable= (tcp_LEmod4G(tcp_conn->
					tc_RCV_NXT, seg_seq) &&
					tcp_Lmod4G(seg_seq, rcv_hi)) ||
					(tcp_LEmod4G(tcp_conn->tc_RCV_NXT,
					seg_seq+data_len-1) &&
					tcp_Lmod4G(seg_seq+data_len-1,
					rcv_hi));
			}
			else
			{
				segm_acceptable= FALSE;
			}
		}
/*
	!segment acceptable ?
		RST ?
			discard packet
			exit
		:
			<SEG=SND.NXT><ACK=RCV.NXT><CTL=ACK>
			exit
*/
		if (!segm_acceptable)
		{
			if (!(tcp_hdr_flags & THF_RST))
			{
				DBLOCK(0x20,
					printf("segment is not acceptable\n");
					printf("\t");
					tcp_print_pack(ip_hdr, tcp_hdr);
					printf("\n\t");
					tcp_print_conn(tcp_conn);
					printf("\n"));
				tcp_conn->tc_flags |= TCF_SEND_ACK;
				tcp_conn_write(tcp_conn, 1);

				/* Sometimes, a retransmission sets the PSH
				 * flag (Solaris 2.4)
				 */
				if (tcp_conn->tc_rcvd_data != NULL &&
					(tcp_hdr_flags & THF_PSH))
				{
					tcp_conn->tc_flags |= TCF_RCV_PUSH;
					if (tcp_conn->tc_fd &&
						(tcp_conn->tc_fd->tf_flags &
						TFF_READ_IP))
					{
						tcp_fd_read(tcp_conn, 1);
					}
				}
			}
			break;
		}
/*
	RST ?
		state == CLOSING || state == LAST-ACK ||
			state == TIME-WAIT ?
			state= CLOSED
			exit
		:
			state= CLOSED
			error "connection reset"
			exit
*/
		if (tcp_hdr_flags & THF_RST)
		{
			if ((tcp_conn->tc_flags &
				(TCF_FIN_SENT|TCF_FIN_RECV)) ==
				(TCF_FIN_SENT|TCF_FIN_RECV) &&
				tcp_conn->tc_send_data == NULL)
			{
				/* Clean shutdown, but the other side
				 * doesn't want to ACK our FIN.
				 */
				tcp_close_connection (tcp_conn, 0);
			}
			else
				tcp_close_connection(tcp_conn, ECONNRESET);
			break;
		}
/*
	SYN in window ?
		state= CLOSED
		error "connection reset"
		exit
*/
		if ((tcp_hdr_flags & THF_SYN) && tcp_GEmod4G(seg_seq,
			tcp_conn->tc_RCV_NXT))
		{
			tcp_close_connection(tcp_conn, ECONNRESET);
			break;
		}
/*
	!ACK ?
		discard packet
		exit
*/
		if (!(tcp_hdr_flags & THF_ACK))
			break;

/*
	SND.UNA < SEG.ACK <= SND.NXT ?
		SND.UNA= SEG.ACK
		reply "send ok"
		SND.WL1 < SEG.SEQ || (SND.WL1 == SEG.SEQ &&
			SND.WL2 <= SEG.ACK ?
			SND.WND= SEG.WND
			SND.Wl1= SEG.SEQ
			SND.WL2= SEG.ACK
	SEG.ACK <= SND.UNA ?
		ignore ACK
	SEG.ACK > SND.NXT ?
		<SEQ=SND.NXT><ACK=RCV.NXT><CTL=ACK>
		discard packet
		exit
*/

		/* Always reset the send timer after a valid ack is
		 * received. The assumption is that either the ack really
		 * acknowledges some data (normal case), contains a zero
		 * window, or the remote host has another reason not
		 * to accept any data. In all cases, the remote host is
		 * alive, so the connection should stay alive too.
		 * Do not reset stt if the state is CLOSING, i.e. if
		 * the user closed the connection and we still have
		 * some data to deliver. We don't want a zero window
		 * to keep us from closing the connection.
		 */
		if (tcp_conn->tc_state != TCS_CLOSING)
			tcp_conn->tc_stt= 0;

		if (seg_ack == tcp_conn->tc_SND_UNA)
		{
			/* This ACK doesn't acknowledge any new data, this
			 * is a likely situation if we are only receiving
			 * data. We only update the window if we are
			 * actually sending or if we currently have a
			 * zero window.
			 */
			if (tcp_conn->tc_snd_cwnd == tcp_conn->tc_SND_UNA &&
				seg_wnd != 0)
			{
				DBLOCK(2, printf("zero window opened\n"));
				/* The other side opened up its receive
				 * window. */
				if (seg_wnd > 2*tcp_conn->tc_mss)
					seg_wnd= 2*tcp_conn->tc_mss;
				tcp_conn->tc_snd_cwnd=
					tcp_conn->tc_SND_UNA+seg_wnd;
				tcp_conn_write(tcp_conn, 1);
			}
			if (seg_wnd == 0)
			{
				tcp_conn->tc_snd_cwnd= tcp_conn->tc_SND_TRM=
					tcp_conn->tc_SND_UNA;
			}
		}
		else if (tcp_Lmod4G(tcp_conn->tc_SND_UNA, seg_ack)
			&& tcp_LEmod4G(seg_ack, tcp_conn->
			tc_SND_NXT))
		{
			tcp_release_retrans(tcp_conn, seg_ack, seg_wnd);
			if (tcp_conn->tc_state == TCS_CLOSED)
				break;
		}
		else if (tcp_Gmod4G(seg_ack,
			tcp_conn->tc_SND_NXT))
		{
			tcp_conn->tc_flags |= TCF_SEND_ACK;
			tcp_conn_write(tcp_conn, 1);
			DBLOCK(1, printf(
			"got an ack of something I haven't send\n");
				printf( "seg_ack= %lu, SND_NXT= %lu\n",
				seg_ack, tcp_conn->tc_SND_NXT));
			break;
		}

/*
	process data...
*/
		tcp_extract_ipopt(tcp_conn, ip_hdr);
		tcp_extract_tcpopt(tcp_conn, tcp_hdr);

		if (data_len)
		{
			if (tcp_LEmod4G(seg_seq, tcp_conn->tc_RCV_NXT))
			{
				process_data (tcp_conn, tcp_hdr,
					tcp_data, data_len);
			}
			else
			{
				process_advanced_data (tcp_conn,
					tcp_hdr, tcp_data, data_len);
			}
			tcp_conn->tc_flags |= TCF_SEND_ACK;
			tcp_conn_write(tcp_conn, 1);

			/* Don't process a FIN if we got new data */
			break;
		}
/*
	FIN ?
		reply pending receives
		advace RCV.NXT over the FIN
		<SEQ=SND.NXT><ACK=RCV.NXT><CTL=ACK>

		state == ESTABLISHED ?
			state= CLOSE-WAIT
		state == FIN-WAIT-1 ?
			state= CLOSING
		state == FIN-WAIT-2 ?
			state= TIME-WAIT
		state == TIME-WAIT ?
			restart the TIME-WAIT timer
	exit
*/
		if ((tcp_hdr_flags & THF_FIN) && tcp_LEmod4G(seg_seq,
			tcp_conn->tc_RCV_NXT))
		{
			if (!(tcp_conn->tc_flags & TCF_FIN_RECV) &&
				tcp_Lmod4G(tcp_conn->tc_RCV_NXT,
				tcp_conn->tc_RCV_HI))
			{
				tcp_conn->tc_RCV_NXT++;
				tcp_conn->tc_flags |= TCF_FIN_RECV;
			}
			tcp_conn->tc_flags |= TCF_SEND_ACK;
			tcp_conn_write(tcp_conn, 1);
			if (tcp_conn->tc_fd &&
				(tcp_conn->tc_fd->tf_flags & TFF_READ_IP))
			{
				tcp_fd_read(tcp_conn, 1);
			}
		}
		break;
	default:
#if !CRAMPED
		printf("tcp_frag2conn: unknown state ");
		tcp_print_state(tcp_conn);
#endif
		break;
	}
	if (tcp_data != NULL)
		bf_afree(tcp_data);
}


PRIVATE void
process_data(tcp_conn, tcp_hdr, tcp_data, data_len)
tcp_conn_t *tcp_conn;
tcp_hdr_t *tcp_hdr;
acc_t *tcp_data;
int data_len;
{
	u32_t lo_seq, hi_seq, urg_seq, seq_nr, adv_seq, nxt;
	u16_t urgptr;
	int tcp_hdr_flags;
	unsigned int offset;
	acc_t *tmp_data, *rcvd_data, *adv_data;
	int len_diff;

	assert(tcp_conn->tc_busy);

	/* Note, tcp_data will be freed by the caller. */
	assert (!(tcp_hdr->th_flags & THF_SYN));

	seq_nr= ntohl(tcp_hdr->th_seq_nr);
	urgptr= ntohs(tcp_hdr->th_urgptr);

	tcp_data->acc_linkC++;

	lo_seq= seq_nr;
	tcp_hdr_flags= tcp_hdr->th_flags & TH_FLAGS_MASK;

	if (tcp_hdr_flags & THF_URG)
	{
		if (urgptr > data_len)
			urgptr= data_len;
		urg_seq= lo_seq+ urgptr;

		if (tcp_GEmod4G(urg_seq, tcp_conn->tc_RCV_HI))
			urg_seq= tcp_conn->tc_RCV_HI;
		if (tcp_conn->tc_flags & TCF_BSD_URG)
		{
			if (tcp_Gmod4G(tcp_conn->tc_RCV_NXT,
				tcp_conn->tc_RCV_LO))
			{
				DBLOCK(1, printf(
					"ignoring urgent data\n"));

				bf_afree(tcp_data);
				/* Should set advertised window to
				 * zero */

				/* Flush */
				tcp_conn->tc_flags |= TCF_RCV_PUSH;
				if (tcp_conn->tc_fd &&
					(tcp_conn->tc_fd->tf_flags &
					TFF_READ_IP))
				{
					tcp_fd_read(tcp_conn, 1);
				}
				return;
			}
		}
		if (tcp_Gmod4G(urg_seq, tcp_conn->tc_RCV_UP))
			tcp_conn->tc_RCV_UP= urg_seq;
		if (urgptr < data_len)
		{
			data_len= urgptr;
			tmp_data= bf_cut(tcp_data, 0, data_len);
			bf_afree(tcp_data);
			tcp_data= tmp_data;
			tcp_hdr_flags &= ~THF_FIN;
		}
		tcp_conn->tc_flags |= TCF_RCV_PUSH;
	}
	else
	{
		/* Normal data. */
	}

	if (tcp_hdr_flags & THF_PSH)
	{
		tcp_conn->tc_flags |= TCF_RCV_PUSH;
	}

	if (tcp_Lmod4G(lo_seq, tcp_conn->tc_RCV_NXT))
	{
		DBLOCK(0x10,
			printf("segment is a retransmission\n"));
		offset= tcp_conn->tc_RCV_NXT-lo_seq;
		tcp_data= bf_delhead(tcp_data, offset);
		lo_seq += offset;
		data_len -= offset;
	}
	assert (lo_seq == tcp_conn->tc_RCV_NXT);

	hi_seq= lo_seq+data_len;
	if (tcp_Gmod4G(hi_seq, tcp_conn->tc_RCV_HI))
	{
		data_len= tcp_conn->tc_RCV_HI-lo_seq;
		tmp_data= bf_cut(tcp_data, 0, data_len);
		bf_afree(tcp_data);
		tcp_data= tmp_data;
		hi_seq= lo_seq+data_len;
		tcp_hdr_flags &= ~THF_FIN;
	}
	assert (tcp_LEmod4G (hi_seq, tcp_conn->tc_RCV_HI));

	rcvd_data= tcp_conn->tc_rcvd_data;
	tcp_conn->tc_rcvd_data= 0;
	tmp_data= bf_append(rcvd_data, tcp_data);
	tcp_conn->tc_rcvd_data= tmp_data;
	tcp_conn->tc_RCV_NXT= hi_seq;

	if ((tcp_hdr_flags & THF_FIN) && 
		tcp_Lmod4G(tcp_conn->tc_RCV_NXT, tcp_conn->tc_RCV_HI))
	{
		tcp_conn->tc_RCV_NXT++;
		tcp_conn->tc_flags |= TCF_FIN_RECV;
	}

	if (tcp_conn->tc_fd && (tcp_conn->tc_fd->tf_flags & TFF_READ_IP))
		tcp_fd_read(tcp_conn, 1);

	DIFBLOCK(2, (tcp_conn->tc_RCV_NXT == tcp_conn->tc_RCV_HI),
		printf("conn[[%d] full receive buffer\n", 
		tcp_conn-tcp_conn_table));

	if (tcp_conn->tc_adv_data == NULL)
		return;
	if (tcp_hdr_flags & THF_FIN)
	{
#if !CRAMPED
		printf("conn[%d]: advanced data after FIN\n",
			tcp_conn-tcp_conn_table);
#endif
		tcp_data= tcp_conn->tc_adv_data;
		tcp_conn->tc_adv_data= NULL;
		bf_afree(tcp_data);
		return;
	}

	lo_seq= tcp_conn->tc_adv_seq;
	if (tcp_Gmod4G(lo_seq, tcp_conn->tc_RCV_NXT))
		return;		/* Not yet */

	tcp_data= tcp_conn->tc_adv_data;
	tcp_conn->tc_adv_data= NULL;

	data_len= bf_bufsize(tcp_data);
	if (tcp_Lmod4G(lo_seq, tcp_conn->tc_RCV_NXT))
	{
		offset= tcp_conn->tc_RCV_NXT-lo_seq;
		if (offset >= data_len)
		{
			bf_afree(tcp_data);
			return;
		}
		tcp_data= bf_delhead(tcp_data, offset);
		lo_seq += offset;
		data_len -= offset;
	}
	assert (lo_seq == tcp_conn->tc_RCV_NXT);

	hi_seq= lo_seq+data_len;
	assert (tcp_LEmod4G (hi_seq, tcp_conn->tc_RCV_HI));

	rcvd_data= tcp_conn->tc_rcvd_data;
	tcp_conn->tc_rcvd_data= 0;
	tmp_data= bf_append(rcvd_data, tcp_data);
	tcp_conn->tc_rcvd_data= tmp_data;
	tcp_conn->tc_RCV_NXT= hi_seq;

	assert (tcp_conn->tc_RCV_LO + bf_bufsize(tcp_conn->tc_rcvd_data) ==
		tcp_conn->tc_RCV_NXT ||
		(tcp_print_conn(tcp_conn), printf("\n"), 0));

	if (tcp_conn->tc_fd && (tcp_conn->tc_fd->tf_flags & TFF_READ_IP))
		tcp_fd_read(tcp_conn, 1);

	adv_data= tcp_conn->tc_adv_data;
	if (adv_data != NULL)
	{
		/* Try to use advanced data. */
		adv_seq= tcp_conn->tc_adv_seq;
		nxt= tcp_conn->tc_RCV_NXT;

		if (tcp_Gmod4G(adv_seq, nxt))
			return;		/* not yet */

		tcp_conn->tc_adv_data= NULL;
		data_len= bf_bufsize(adv_data);

		if (tcp_Lmod4G(adv_seq, nxt))
		{
			if (tcp_LEmod4G(adv_seq+data_len, nxt))
			{
				/* Data is not needed anymore. */
				bf_afree(adv_data);
				return;
			}

			len_diff= nxt-adv_seq;
			adv_data= bf_delhead(adv_data, len_diff);
			data_len -= len_diff;
		}

		DBLOCK(1, printf("using advanced data\n"));

		/* Append data to the input buffer */
		if (tcp_conn->tc_rcvd_data == NULL)
		{
			tcp_conn->tc_rcvd_data= adv_data;
		}
		else
		{
			tcp_conn->tc_rcvd_data=
				bf_append(tcp_conn->tc_rcvd_data, adv_data);
		}
		tcp_conn->tc_SND_NXT += data_len;
		assert(tcp_check_conn(tcp_conn));

		if (tcp_conn->tc_fd &&
			(tcp_conn->tc_fd->tf_flags & TFF_READ_IP))
		{
			tcp_fd_read(tcp_conn, 1);
		}
	}
}

PRIVATE void process_advanced_data(tcp_conn, tcp_hdr, tcp_data, data_len)
tcp_conn_t *tcp_conn;
tcp_hdr_t *tcp_hdr;
acc_t *tcp_data;
int data_len;
{
	u32_t seq, adv_seq;
	acc_t *adv_data;

	assert(tcp_conn->tc_busy);

	/* Note, tcp_data will be freed by the caller. */

	/* Always send an ACK, this allows the sender to do a fast
	 * retransmit.
	 */
	tcp_conn->tc_flags |= TCF_SEND_ACK;
	tcp_conn_write(tcp_conn, 1);

	if (tcp_hdr->th_flags & THF_URG)
		return;	/* Urgent data is to complicated */
	if (tcp_hdr->th_flags & THF_PSH)
		tcp_conn->tc_flags |= TCF_RCV_PUSH;
	seq= ntohl(tcp_hdr->th_seq_nr);

	/* Make sure that the packet doesn't fall outside of the window
	 * we offered.
	 */
	if (tcp_Gmod4G(seq+data_len, tcp_conn->tc_RCV_HI))
		return;

	adv_data= tcp_conn->tc_adv_data;
	adv_seq= tcp_conn->tc_adv_seq;
	tcp_conn->tc_adv_data= NULL;

	tcp_data->acc_linkC++;
	if (adv_data == NULL)
	{
		adv_seq= seq;
		adv_data= tcp_data;
	}
	else if (seq + data_len == adv_seq)
	{
		/* New data fits right before exiting data. */
		adv_data= bf_append(tcp_data, adv_data);
		adv_seq= seq;
	}
	else if (adv_seq + bf_bufsize(adv_data) == seq)
	{
		/* New data fits right after exiting data. */
		adv_data= bf_append(adv_data, tcp_data);
	}
	else
	{
		/* New data doesn't fit. */
		bf_afree(tcp_data);
	}
	tcp_conn->tc_adv_data= adv_data;
	tcp_conn->tc_adv_seq= adv_seq;
}
				
PRIVATE void create_RST(tcp_conn, ip_hdr, tcp_hdr, data_len)
tcp_conn_t *tcp_conn;
ip_hdr_t *ip_hdr;
tcp_hdr_t *tcp_hdr;
int data_len;
{
	acc_t *tmp_ipopt, *tmp_tcpopt, *tcp_pack;
	ip_hdropt_t ip_hdropt;
	tcp_hdropt_t tcp_hdropt;
	acc_t *RST_acc;
	ip_hdr_t *RST_ip_hdr;
	tcp_hdr_t *RST_tcp_hdr;
	char *ptr2RSThdr;
	size_t pack_size, ip_hdr_len;

	DBLOCK(0x10, printf("in create_RST, bad pack is:\n"); 
		tcp_print_pack(ip_hdr, tcp_hdr); tcp_print_state(tcp_conn);
		printf("\n"));

	assert(tcp_conn->tc_busy);

	/* Only send RST packets in reponse to actual data (or SYN, FIN)
	 * this solves a problem during connection shutdown. The problem
	 * is the follow senario: a senders closes the connection instead
	 * of doing a shutdown and waiting for the receiver to shutdown.
	 * The receiver is slow in processing the last data. After the
	 * sender has completely closed the connection, the receiver
	 * sends a window update which triggers the sender to send a
	 * RST. The receiver closes the connection in reponse to the RST.
	 */
	if ((tcp_hdr->th_flags & (THF_FIN|THF_SYN)) == 0 &&
		data_len == 0)
	{
#if DEBUG
 { printf("tcp_recv`create_RST: no data, no RST\n"); }
#endif
		return;
	}

	tmp_ipopt= tcp_conn->tc_remipopt;
	if (tmp_ipopt)
		tmp_ipopt->acc_linkC++;
	tmp_tcpopt= tcp_conn->tc_tcpopt;
	if (tmp_tcpopt)
		tmp_tcpopt->acc_linkC++;

	tcp_extract_ipopt (tcp_conn, ip_hdr);
	tcp_extract_tcpopt (tcp_conn, tcp_hdr);

	RST_acc= tcp_make_header (tcp_conn, &RST_ip_hdr, &RST_tcp_hdr,
		(acc_t *)0);

	if (tcp_conn->tc_remipopt)
		bf_afree(tcp_conn->tc_remipopt);
	tcp_conn->tc_remipopt= tmp_ipopt;
	if (tcp_conn->tc_tcpopt)
		bf_afree(tcp_conn->tc_tcpopt);
	tcp_conn->tc_tcpopt= tmp_tcpopt;

	RST_ip_hdr->ih_src= ip_hdr->ih_dst;
	RST_ip_hdr->ih_dst= ip_hdr->ih_src;

	RST_tcp_hdr->th_srcport= tcp_hdr->th_dstport;
	RST_tcp_hdr->th_dstport= tcp_hdr->th_srcport;
	if (tcp_hdr->th_flags & THF_ACK)
	{
		RST_tcp_hdr->th_seq_nr= tcp_hdr->th_ack_nr;
		RST_tcp_hdr->th_flags= THF_RST;
	}
	else
	{
		RST_tcp_hdr->th_seq_nr= 0;
		RST_tcp_hdr->th_ack_nr=
			htonl(
				ntohl(tcp_hdr->th_seq_nr)+
				data_len +
				(tcp_hdr->th_flags & THF_SYN ? 1 : 0) +
				(tcp_hdr->th_flags & THF_FIN ? 1 : 0));
		RST_tcp_hdr->th_flags= THF_RST|THF_ACK;
	}

	pack_size= bf_bufsize(RST_acc);
	RST_ip_hdr->ih_length= htons(pack_size);
	RST_tcp_hdr->th_window= htons(tcp_conn->tc_rcv_wnd);
	RST_tcp_hdr->th_chksum= 0;

	RST_acc->acc_linkC++;
	ip_hdr_len= (ip_hdr->ih_vers_ihl & IH_IHL_MASK) << 2;
	tcp_pack= bf_delhead(RST_acc, ip_hdr_len);
	RST_tcp_hdr->th_chksum= ~tcp_pack_oneCsum (RST_ip_hdr, tcp_pack);
	bf_afree(tcp_pack);
	
	DBLOCK(2, tcp_print_pack(ip_hdr, tcp_hdr); printf("\n");
		tcp_print_pack(RST_ip_hdr, RST_tcp_hdr); printf("\n"));

	if (tcp_conn->tc_frag2send)
		bf_afree(tcp_conn->tc_frag2send);
	tcp_conn->tc_frag2send= RST_acc;
	tcp_conn_write(tcp_conn, 1);
}

PUBLIC void
tcp_fd_read(tcp_conn, enq)
tcp_conn_t *tcp_conn;
int enq;					/* Enqueue writes. */
{
	tcp_fd_t *tcp_fd;
	size_t data_size, read_size;
	acc_t *data;
	int fin_recv, urg, push, result;
	i32_t old_window, new_window;

	assert(tcp_conn->tc_busy);

	tcp_fd= tcp_conn->tc_fd;

	assert (tcp_fd->tf_flags & TFF_READ_IP);
	if (tcp_conn->tc_state == TCS_CLOSED)
	{
		if (tcp_fd->tf_read_offset)
			tcp_reply_read (tcp_fd, tcp_fd->tf_read_offset);
		else
			tcp_reply_read (tcp_fd, tcp_conn->tc_error);
		return;
	}

	urg= tcp_Gmod4G(tcp_conn->tc_RCV_UP, tcp_conn->tc_RCV_LO);
	push= (tcp_conn->tc_flags & TCF_RCV_PUSH);
	fin_recv= (tcp_conn->tc_flags & TCF_FIN_RECV);

	data_size= tcp_conn->tc_RCV_NXT-tcp_conn->tc_RCV_LO;
	if (fin_recv)
		data_size--;
	if (urg)
		read_size= tcp_conn->tc_RCV_UP-tcp_conn->tc_RCV_LO;
	else
		read_size= data_size;

	if (read_size >= tcp_fd->tf_read_count)
		read_size= tcp_fd->tf_read_count;
	else if (!push && !fin_recv && !urg &&
		data_size < TCP_MIN_RCV_WND_SIZE)
	{
		/* Defer the copy out until later. */
		return;
	}
	else if (data_size == 0 && !fin_recv)
	{
		/* No data, and no end of file. */
		return;
	}

	if (read_size)
	{
		if (urg && !(tcp_fd->tf_flags & TFF_RECV_URG))
		{
			if (tcp_fd->tf_read_offset)
			{
				tcp_reply_read (tcp_fd,
					tcp_fd->tf_read_offset);
			}
			else
			{
				tcp_reply_read (tcp_fd, EURG);
			}
			return;
		}
		else if (!urg && (tcp_fd->tf_flags & TFF_RECV_URG))
		{
			if (tcp_fd->tf_read_offset)
			{
				tcp_reply_read (tcp_fd,
					tcp_fd->tf_read_offset);
			}
			else
			{
				tcp_reply_read(tcp_fd, ENOURG);
			}
			return;
		}

		if (read_size == data_size)
		{
			data= tcp_conn->tc_rcvd_data;
			data->acc_linkC++;
		}
		else
		{
			data= bf_cut(tcp_conn->tc_rcvd_data, 0, read_size);
		}
		result= (*tcp_fd->tf_put_userdata) (tcp_fd->tf_srfd,
			tcp_fd->tf_read_offset, data, FALSE);
		if (result<0)
		{
			if (tcp_fd->tf_read_offset)
				tcp_reply_read(tcp_fd, tcp_fd->
					tf_read_offset);
			else
				tcp_reply_read(tcp_fd, result);
			return;
		}
		tcp_fd->tf_read_offset += read_size;
		tcp_fd->tf_read_count -= read_size;

		if (data_size == read_size)
		{
			bf_afree(tcp_conn->tc_rcvd_data);
			tcp_conn->tc_rcvd_data= 0;
		}
		else
		{
			tcp_conn->tc_rcvd_data=
				bf_delhead(tcp_conn->tc_rcvd_data,
				read_size);
		}
		tcp_conn->tc_RCV_LO += read_size;
		data_size -= read_size;
	}
	if (tcp_conn->tc_RCV_HI-tcp_conn->tc_RCV_LO <= (tcp_conn->
		tc_rcv_wnd-tcp_conn->tc_mss))
	{
		old_window= tcp_conn->tc_RCV_HI-tcp_conn->tc_RCV_NXT;
		tcp_conn->tc_RCV_HI= tcp_conn->tc_RCV_LO + 
			tcp_conn->tc_rcv_wnd;
		new_window= tcp_conn->tc_RCV_HI-tcp_conn->tc_RCV_NXT;
		assert(old_window >=0 && new_window >= old_window);
		if (old_window < tcp_conn->tc_mss &&
			new_window >= tcp_conn->tc_mss)
		{
			tcp_conn->tc_flags |= TCF_SEND_ACK;
			DBLOCK(2, printf("opening window\n"));
			tcp_conn_write(tcp_conn, 1);
		}
	}
	if (tcp_conn->tc_rcvd_data == NULL &&
		tcp_conn->tc_adv_data == NULL)
	{
		/* Out of data, clear PUSH flag and reply to a read. */
		tcp_conn->tc_flags &= ~TCF_RCV_PUSH;
		if (tcp_fd->tf_read_offset)
		{
			tcp_reply_read (tcp_fd, tcp_fd->tf_read_offset);
			return;
		}
	}
	if (fin_recv || urg || !tcp_fd->tf_read_count)
	{
		tcp_reply_read (tcp_fd, tcp_fd->tf_read_offset);
		return;
	}
}

/*
 * $PchId: tcp_recv.c,v 1.13 1997/01/31 08:49:51 philip Exp $
 */
