/*
    Copyright 2000 (c) by Reto Gaehler, Toni Kaufmann,
    Swiss Federal Institute of Technology,
    Computer Engineering and Networks Laboratory.
 
    TOPSY -- A Teachable Operating System.
             Implementation of a tiny and simple
             micro kernel for teaching purposes.
 
    For further information, please visit http://www.tik.ee.ethz.ch/~topsy
 
    This software is provided under the terms of the GNU General Public 
    Licence. A full copy of the GNU GPL is provided in the file COPYING 
    found in the development root of Topsy.
*/
/*
    File:                  $Source: /home/ruf/cvs_Topsy/cvs/Topsy/Net/old/TCP/nettcpTCP.c,v $
    Author(s):             
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 2936 $
    Creation Date:         
    Last Date of Change:   $Date: 2007-03-21 15:23:42 +0100 (Mit, 21 Mär 2007) $      by: $Author: baderma $
 
 
    $Log: nettcpTCP.c,v $
    Revision 1.3  2000/11/15 10:12:15  ruf
    Repository Reorganisation
 
    Revision 1.2  2000/04/03 17:45:30  gfa
    *** empty log message ***
 
    Revision 1.1  2000/03/31 17:50:39  gfa
    Merged with /Net from several term projects
 
*/

//	This module handles the whole tcp protocol standard.
//	It's derived from the implementation used by Xinu, written
//	by Douglas E. Comer and David L. Stevens.
//	ftp://ftp.cs.purdue.edu/pub/comer/

#define MAX_ETHER_IP_HEADER_LEN	40

#include "nettcpTimer.h"
#include "nettcpTCP.h"
#include "nettcpMain.h"
#include "nettcpSocket.h"

#include <IP/NetIP.h>
#include <IP/NetIPchecksum.h>

//**************************************************************************************************
//	type definitions
//**************************************************************************************************
typedef struct
{
  NetBufHdr 		netbuf;
  IPAddressUon		uonSrcAddr;
  IPAddressUon		uonDstAddr;
  unsigned char 	u8Zero;
  unsigned char 	u8Protocol;
  unsigned short 	u16Len;
}
TCPPseudoHdrRec;

//**************************************************************************************************
//	Prototypes
//**************************************************************************************************

void tcpPacketDump(TCPSegPtr pTCPSeg,char *pn8Info,int nNum);
static void tcpHeaderNet2Host(TCPSegPtr pTCPSeg);
static void tcpHeaderHost2Net(TCPSegPtr pTCPSeg);
static TCBPtr tcpDemux(PacketPtr pPacket);
static unsigned short tcpCheckSum(NetBuf buf,NetAttrs attrs,int len);
static int tcpRoundTripTime(TCBPtr pTCB);
static int tcpFragmentInsert(TCBPtr pTCB,long nSeq,unsigned uDataLen);
static int tcpFragmentJoin(TCBPtr pTCB,PacketPtr pPacket,unsigned uDataLen);
static Boolean tcpIsOK(TCBPtr pTCB,PacketPtr pPacket);
static int tcpSndMaxSegSize(TCBPtr pTCB,PacketPtr pPacket,unsigned char *pu8Option);
static int tcpOptions(TCBPtr pTCB,PacketPtr pPacket);
static int tcpReset(PacketPtr pPacket);
static int tcpSndWindow(TCBPtr pTCB,PacketPtr pPacket);
static int tcpAbort(TCBPtr pTCB,int nError);
static int tcpHowMuch(TCBPtr pTCB);
static int tcpSendLen(TCBPtr pTCB,Boolean bRetransmit,unsigned int *puOffset);
static void tcpRcvMaxSegSize(TCBPtr pTCB,TCPSegPtr pTCPSeg);
static int tcpSend(TCBPtr pTCB,Boolean bRetransmit);
static int tcpReTransmit(TCBPtr pTCB,unsigned long u32Event);
static int tcpTransmit(TCBPtr pTCB,unsigned long u32Event);
static int tcpIdle(TCBPtr pTCB,unsigned long u32Event);
static int tcpPersist(TCBPtr pTCB,unsigned long u32Event);
static void tcpOutDispatcher(TCBPtr pTCB,unsigned long u32Event);
static int tcpOutputState(TCBPtr pTCB,int nAcked);
static int tcpAcked(TCBPtr pTCB,PacketPtr pPacket);
static int tcpAckIt(TCBPtr pTCB,PacketPtr pPacket);
static int tcpClosed(TCBPtr pTCB,PacketPtr pPacket);
static int tcpWindowInit(TCBPtr pTCB,TCBPtr pNewTCB,PacketPtr pPacket);
static int tcpDoData(TCBPtr pTCB,PacketPtr pPacket,long n32First,unsigned uDataLen);
static int tcpData(TCBPtr pTCB,PacketPtr pPacket);
static int tcpDoListen(TCBPtr pTCB,PacketPtr pPacket);
static int tcpKillTimers(TCBPtr pTCB);
static int tcpSynSent(TCBPtr pTCB,PacketPtr pPacket);
static int tcpSynReceived(TCBPtr pTCB,PacketPtr pPacket);
static int tcpEstablished(TCBPtr pTCB,PacketPtr pPacket);
static int tcpWait(TCBPtr pTCB);
static int tcpFin1(TCBPtr pTCB,PacketPtr pPacket);
static int tcpFin2(TCBPtr pTCB,PacketPtr pPacket);
static int tcpCloseWait(TCBPtr pTCB,PacketPtr pPacket);
static int tcpLastAck(TCBPtr pTCB,PacketPtr pPacket);
static int tcpClosing(TCBPtr pTCB,PacketPtr pPacket);
static int tcpTimeWait(TCBPtr pTCB,PacketPtr pPacket);
static void tcpInputStateDispatcher(TCBPtr pTCB,PacketPtr pPacket);
static long tcpInitialSequence(void);

//**************************************************************************************************
//	tcp globals
//**************************************************************************************************

extern SemaphoreRec	gTCBLinkedListMutex;
extern PortRec		gOutputPort;
TCBPtr				gTCPTCBList = NULL;			// head of TCB linked list

// statistics
unsigned long	gu32TcpInErrors = 0;
unsigned long	gu32TcpPassiveOpens = 0;
unsigned long	gu32TcpAttemptFails = 0;
unsigned long	gu32TcpCurrEstab = 0;
unsigned long	gu32TcpEstabResets = 0;
unsigned long	gu32TcpOutSegs = 0;
unsigned long	gu32TcpOutRsts = 0;
unsigned long	gu32TcpRetransSegs = 0;
unsigned long	gu32TcpActiveOpens = 0;

//**************************************************************************************************
//	tcpAlloc
//**************************************************************************************************
unsigned char *tcpAlloc(unsigned int nSize)
{
  int 			nRes;
  NetBuf 			pBuf;

  nRes=netbufAlloc(&pBuf,nSize);

  if (nRes>0)
  {
    if (pBuf->next!=NULL)
    {
      netbufFree(pBuf);
      return NULL;
    }
    return ((unsigned char *)pBuf)+sizeof(NetBufHdr);
  }
  return NULL;
}
//**************************************************************************************************
//	tcpFree
//**************************************************************************************************
void tcpFree(unsigned char *pBuf)
{
  netbufFree((NetBuf)(pBuf-sizeof(NetBufHdr)));
}

//**************************************************************************************************
//	tcpGetIPAddr
//	retrieves either the source or destination ip addr from the net attrs
//**************************************************************************************************
void
tcpPacketDump(
  TCPSegPtr 	pTCPSeg,		// pointer to tcp segmen
  char		*pn8Info,		// additional info as c string
  int			nNum)			// additional info as number
{
  if (((*(short*)pn8Info==*(short*)"SR") && (TCP_DUMP_MASK & TCP_DUMP_SEND_RESET)) ||
      ((*(short*)pn8Info==*(short*)"SN") && (TCP_DUMP_MASK & TCP_DUMP_SEND_DATA)) ||
      ((*(short*)pn8Info==*(short*)"SA") && (TCP_DUMP_MASK & TCP_DUMP_SEND_ACK)) ||
      ((*(short*)pn8Info==*(short*)"RN") && (TCP_DUMP_MASK & TCP_DUMP_RECEIVE)))
  {
    ThreadId myThread,parentThread;
    tmGetInfo(SELF,&myThread,&parentThread);

    netdbgPrintf(NETDEBUG_TCP,(char*)pn8Info);
    netdbgPrintf(NETDEBUG_TCP," id:%d",myThread);
    netdbgPrintf(NETDEBUG_TCP," s:%d",pTCPSeg->u16SrcPort);
    netdbgPrintf(NETDEBUG_TCP," d:%d",pTCPSeg->u16DstPort);
    netdbgPrintf(NETDEBUG_TCP," seq:%d",pTCPSeg->n32Seq);
    netdbgPrintf(NETDEBUG_TCP," ack:%d",pTCPSeg->n32AckSeq);
    netdbgPrintf(NETDEBUG_TCP," off:%d",pTCPSeg->u8Offset);
    if (pTCPSeg->u8Code & TCP_CODE_URG)
      netdbgPrintf(NETDEBUG_TCP," URG");
    if (pTCPSeg->u8Code & TCP_CODE_ACK)
      netdbgPrintf(NETDEBUG_TCP," ACK");
    if (pTCPSeg->u8Code & TCP_CODE_PSH)
      netdbgPrintf(NETDEBUG_TCP," PSH");
    if (pTCPSeg->u8Code & TCP_CODE_RST)
      netdbgPrintf(NETDEBUG_TCP," RST");
    if (pTCPSeg->u8Code & TCP_CODE_SYN)
      netdbgPrintf(NETDEBUG_TCP," SYN");
    if (pTCPSeg->u8Code & TCP_CODE_FIN)
      netdbgPrintf(NETDEBUG_TCP," FIN");
    netdbgPrintf(NETDEBUG_TCP," win:%d",pTCPSeg->u16Window);
    netdbgPrintf(NETDEBUG_TCP," chk:%d",pTCPSeg->u16CheckSum);
    netdbgPrintf(NETDEBUG_TCP," urp:%d",pTCPSeg->u16Urgent);
    netdbgPrintf(NETDEBUG_TCP," n:%d",nNum);
    netdbgPrintf(NETDEBUG_TCP,"\n");
  }
}

//**************************************************************************************************
//	tcpGetIPAddr
//	retrieves either the source or destination ip addr from the net attrs
//**************************************************************************************************
int									// result code
tcpGetIPAddr(
  NetAttrs		attrs,				// net attributes
  IPAddrSet		setType,			// source or dest address
  IPAddressPtr	pIpAddr)			// -> returns ip address
{
  unsigned short tmp;
  switch(setType)
  {
  case TCP_IP_SRC_ADDR:
    {
      if(!netattrGet(attrs,NETATTR_IP_FROM_0,&tmp))
        return TCP_GENERAL_ERROR;
      pIpAddr->recAddr.u8Addr0 = tmp;
      if(!netattrGet(attrs,NETATTR_IP_FROM_1,&tmp))
        return TCP_GENERAL_ERROR;
      pIpAddr->recAddr.u8Addr1 = tmp;
      if(!netattrGet(attrs,NETATTR_IP_FROM_2,&tmp))
        return TCP_GENERAL_ERROR;
      pIpAddr->recAddr.u8Addr2 = tmp;
      if(!netattrGet(attrs,NETATTR_IP_FROM_3,&tmp))
        return TCP_GENERAL_ERROR;
      pIpAddr->recAddr.u8Addr3 = tmp;
    }
    break;
  case TCP_IP_DST_ADDR:
    {
      if(!netattrGet(attrs,NETATTR_IP_TO_0,&tmp))
        return TCP_GENERAL_ERROR;
      pIpAddr->recAddr.u8Addr0 = tmp;
      if(!netattrGet(attrs,NETATTR_IP_TO_1,&tmp))
        return TCP_GENERAL_ERROR;
      pIpAddr->recAddr.u8Addr1 = tmp;
      if(!netattrGet(attrs,NETATTR_IP_TO_2,&tmp))
        return TCP_GENERAL_ERROR;
      pIpAddr->recAddr.u8Addr2 = tmp;
      if(!netattrGet(attrs,NETATTR_IP_TO_3,&tmp))
        return TCP_GENERAL_ERROR;
      pIpAddr->recAddr.u8Addr3 = tmp;
    }
    break;
  default:
    break;
  }

  return TCP_OK;
}

//**************************************************************************************************
//	tcpSetIPAddr
//	puts either the source or destination ip addr into the net attrs
//**************************************************************************************************
int									// result code
tcpSetIPAddr(
  NetAttrs		attrs,
  IPAddrSet		setType,			// source or dest address
  IPAddressPtr	pIpAddr)			// ip address
{
  switch(setType)
  {
  case TCP_IP_SRC_ADDR:
    {
      if(!netattrSet(attrs,NETATTR_IP_FROM_0,pIpAddr->recAddr.u8Addr0))
        return TCP_GENERAL_ERROR;
      if(!netattrSet(attrs,NETATTR_IP_FROM_1,pIpAddr->recAddr.u8Addr1))
        return TCP_GENERAL_ERROR;
      if(!netattrSet(attrs,NETATTR_IP_FROM_2,pIpAddr->recAddr.u8Addr2))
        return TCP_GENERAL_ERROR;
      if(!netattrSet(attrs,NETATTR_IP_FROM_3,pIpAddr->recAddr.u8Addr3))
        return TCP_GENERAL_ERROR;
    }
    break;
  case TCP_IP_DST_ADDR:
    {
      if(!netattrSet(attrs,NETATTR_IP_TO_0,pIpAddr->recAddr.u8Addr0))
        return TCP_GENERAL_ERROR;
      if(!netattrSet(attrs,NETATTR_IP_TO_1,pIpAddr->recAddr.u8Addr1))
        return TCP_GENERAL_ERROR;
      if(!netattrSet(attrs,NETATTR_IP_TO_2,pIpAddr->recAddr.u8Addr2))
        return TCP_GENERAL_ERROR;
      if(!netattrSet(attrs,NETATTR_IP_TO_3,pIpAddr->recAddr.u8Addr3))
        return TCP_GENERAL_ERROR;
    }
    break;
  default:
    break;
  }
  return TCP_OK;
}

//**************************************************************************************************
//	tcpHeaderNet2Host
//	transfers byte order of header from host to network
//**************************************************************************************************
static void
tcpHeaderNet2Host(
  TCPSegPtr	pTCPSeg)		// pointer to tcp segment
{
  pTCPSeg->u16SrcPort = ntohs(pTCPSeg->u16SrcPort);
  pTCPSeg->u16DstPort = ntohs(pTCPSeg->u16DstPort);
  pTCPSeg->n32Seq = ntohl(pTCPSeg->n32Seq);
  pTCPSeg->n32AckSeq = ntohl(pTCPSeg->n32AckSeq);
  pTCPSeg->u16Window = ntohs(pTCPSeg->u16Window);
  pTCPSeg->u16Urgent = ntohs(pTCPSeg->u16Urgent);
}

//**************************************************************************************************
//	tcpHeaderHost2Net
//	transfers byte order of header from network to host
//**************************************************************************************************
static void
tcpHeaderHost2Net(
  TCPSegPtr	pTCPSeg)		// pointer to tcp segment
{
  pTCPSeg->u16SrcPort = htons(pTCPSeg->u16SrcPort);
  pTCPSeg->u16DstPort = htons(pTCPSeg->u16DstPort);
  pTCPSeg->n32Seq = htonl(pTCPSeg->n32Seq);
  pTCPSeg->n32AckSeq = htonl(pTCPSeg->n32AckSeq);
  pTCPSeg->u16Window = htons(pTCPSeg->u16Window);
  pTCPSeg->u16Urgent = htons(pTCPSeg->u16Urgent);
}

//**************************************************************************************************
//	tcpDemux
//	does TCP port demultiplexing
//**************************************************************************************************
static TCBPtr				// returns tcb which belongs to the given tcp packet or NULL
tcpDemux(
  PacketPtr pPacket)			// tcp packet
{
  TCBPtr 			pCurrentTCB;
  TCBPtr			pListenTCB;
  IPAddressUon	uonSegSrcIpAddr,uonSegDstIpAddr;

  // gets source and destination ip address of segment
  if (tcpGetIPAddr(pPacket->netAttrs,TCP_IP_SRC_ADDR,&uonSegSrcIpAddr) != TCP_OK)
    return NULL;
  if (tcpGetIPAddr(pPacket->netAttrs,TCP_IP_DST_ADDR,&uonSegDstIpAddr) != TCP_OK)
    return NULL;

  semWait(&gTCBLinkedListMutex);
  pCurrentTCB = gTCPTCBList;
  pListenTCB = NULL;
  while (pCurrentTCB != NULL)
  {
    unsigned short	u16TCBSrcPort,u16TCBDstPort;
    IPAddressUon	uonTCBSrcIpAddr,uonTCBDstIpAddr;

    // gets source and destination port of tcb
    if (!netattrGet(pCurrentTCB->netParams,NETATTR_TCP_FROM,&u16TCBSrcPort))
      return NULL;
    if (!netattrGet(pCurrentTCB->netParams,NETATTR_TCP_TO,&u16TCBDstPort))
      return NULL;

    // gets source and destination ip address of tcb
    if (tcpGetIPAddr(pCurrentTCB->netParams,TCP_IP_SRC_ADDR,&uonTCBSrcIpAddr) != TCP_OK)
      return NULL;
    if (tcpGetIPAddr(pCurrentTCB->netParams,TCP_IP_DST_ADDR,&uonTCBDstIpAddr) != TCP_OK)
      return NULL;

    if ((pPacket->pTCPSeg->u16DstPort == u16TCBSrcPort) &&
        (pPacket->pTCPSeg->u16SrcPort == u16TCBDstPort) &&
        (uonSegSrcIpAddr.u32Addr == uonTCBDstIpAddr.u32Addr) &&
        (uonSegDstIpAddr.u32Addr == uonTCBSrcIpAddr.u32Addr) )
      break;

    if ((pCurrentTCB->setState == TCP_STATE_LISTEN) &&
        (pPacket->pTCPSeg->u16DstPort == u16TCBSrcPort) )
      pListenTCB = pCurrentTCB;
    pCurrentTCB = (TCBPtr)pCurrentTCB->pNext;
  }

  if ((pCurrentTCB == NULL) && (pPacket->pTCPSeg->u8Code & TCP_CODE_SYN))
    pCurrentTCB = pListenTCB;

  semSignal(&gTCBLinkedListMutex);

  if (pCurrentTCB == NULL)
    return NULL;
  semWait(&pCurrentTCB->recMutex);

  return pCurrentTCB;
}

//**************************************************************************************************
//	tcpCheckSum
//	calculates tcp checksum.
//**************************************************************************************************
static unsigned short
tcpCheckSum(
  NetBuf 		buf,
  NetAttrs 	attrs,
  int			len)				// data length
{
  TCPPseudoHdrRec	recTcpPseudoHdr;

  // Constructs the Pseudo-header by adding a FALSE-NetBuf
  recTcpPseudoHdr.netbuf.start = NETBUF_HEADERSIZE;
  recTcpPseudoHdr.netbuf.end  = recTcpPseudoHdr.netbuf.start +
                                sizeof(TCPPseudoHdrRec) - NETBUF_HEADERSIZE;
  recTcpPseudoHdr.netbuf.next = buf;

  // source address
  if (tcpGetIPAddr(attrs,TCP_IP_SRC_ADDR,&recTcpPseudoHdr.uonSrcAddr) != TCP_OK)
    return TCP_GENERAL_ERROR;

  // dest address
  if (tcpGetIPAddr(attrs,TCP_IP_DST_ADDR,&recTcpPseudoHdr.uonDstAddr) != TCP_OK)
    return TCP_GENERAL_ERROR;

  recTcpPseudoHdr.u8Zero = 0;
  recTcpPseudoHdr.u8Protocol = NETIP_PROTOCOL_TCP;
  recTcpPseudoHdr.u16Len = htons(len);

  // calculate checksum
  return netipChecksum(&recTcpPseudoHdr.netbuf,len + sizeof(TCPPseudoHdrRec)-NETBUF_HEADERSIZE);
}

//**************************************************************************************************
//	tcpRoundTripTime
//**************************************************************************************************
static int
tcpRoundTripTime(
  TCBPtr	pTCB)
{
  int	nRoundTripTime;
  int	nDelta;

  nRoundTripTime = tcp_tmClear(&gOutputPort,pTCB,RETRANSMIT);

  if (nRoundTripTime != TIMER_FAILED && pTCB->setOutState != TCP_OUT_STATE_RETRANSMIT)
  {
    if (pTCB->nSmoothedRoundTripTime == 0)
      pTCB->nSmoothedRoundTripTime = nRoundTripTime << 3;
    nDelta = nRoundTripTime - (pTCB->nSmoothedRoundTripTime >> 3);
    pTCB->nSmoothedRoundTripTime += nDelta;
    if (nDelta < 0)
      nDelta -=nDelta;
    pTCB->nRoundTripDeviationEst += nDelta - (pTCB->nRoundTripDeviationEst >> 2);

    pTCB->nRetransmitTimeout = ((pTCB->nSmoothedRoundTripTime >> 2) +
                                pTCB->nRoundTripDeviationEst) >> 1;

    if (pTCB->nRetransmitTimeout < TCP_MINRXT)
      pTCB->nRetransmitTimeout = TCP_MINRXT;
  }
  if (pTCB->u32CongestionWinSize < pTCB->u32SlowStartThreshold)
    pTCB->u32CongestionWinSize += pTCB->u32SndMaxSegSize;
  else
    pTCB->u32CongestionWinSize += (pTCB->u32SndMaxSegSize * pTCB->u32SndMaxSegSize) /
                                  pTCB->u32CongestionWinSize;
  return TCP_OK;
}

//**************************************************************************************************
//	tcpFragmentInsert
//	add a new TCP segment fragment to a TCB sequence queue
//**************************************************************************************************
static int
tcpFragmentInsert(
  TCBPtr 		pTCB,
  long		nSeq,
  unsigned 	uDataLen)
{
  TCPFragmentPtr	pFragment;

  if (uDataLen == 0)
    return TCP_OK;

  pFragment=(TCPFragmentPtr)tcpAlloc(sizeof(TCPFragmentRec));
  if (pFragment == NULL)
    return TCP_GENERAL_ERROR;
  pFragment->n32Seq = nSeq;
  pFragment->nLen = uDataLen;
  if (pTCB->nRcvSegFragQueue < 0)
    pTCB->nRcvSegFragQueue = pqNewq(TCP_FRAGMENT_QUEUE_SIZE);
  if (pqEnq(pTCB->nRcvSegFragQueue,pFragment,-pFragment->n32Seq) < 0)
    tcpFree((unsigned char *)pFragment);

  return TCP_OK;
}

//**************************************************************************************************
//	tcpFragmentJoin
//	joins TCP fragments
//**************************************************************************************************
static int
tcpFragmentJoin(
  TCBPtr 		pTCB,
  PacketPtr	pPacket,
  unsigned	uDataLen)
{
  TCPFragmentPtr	pFragment = NULL;
  int				nNewCount;

  pTCB->n32RcvNextSeq += uDataLen;
  pTCB->u32RcvBufCount += uDataLen;
  if (pTCB->n32RcvNextSeq == pTCB->n32FINSeqNum)
    goto alldone;

  if ((pTCB->n32RcvNextSeq - pTCB->n32PUSHSeqNum) >= 0)
  {
    pPacket->pTCPSeg->u8Code |= TCP_CODE_PSH;
    pTCB->n32PUSHSeqNum = 0;
  }

  if (pTCB->nRcvSegFragQueue < 0)
    return TCP_OK;

  pFragment = (TCPFragmentPtr)pqDeq(pTCB->nRcvSegFragQueue);
  if (pFragment == NULL)
    return TCP_OK;

  while ((pFragment->n32Seq - pTCB->n32RcvNextSeq) <= 0)
  {
    nNewCount = pFragment->nLen - (pTCB->n32RcvNextSeq - pFragment->n32Seq);
    if (nNewCount > 0)
    {
      pTCB->n32RcvNextSeq += nNewCount;
      pTCB->u32RcvBufCount += nNewCount	;
    }

    if (pTCB->n32RcvNextSeq == pTCB->n32FINSeqNum)
      goto alldone;

    if ((pTCB->n32RcvNextSeq - pTCB->n32PUSHSeqNum) >= 0)
    {
      pPacket->pTCPSeg->u8Code |= TCP_CODE_PSH;
      pTCB->n32PUSHSeqNum = 0;
    }

    tcpFree((unsigned char *)pFragment);
    pFragment = (TCPFragmentPtr)pqDeq(pTCB->nRcvSegFragQueue);

    if (pFragment == NULL)
    {
      pqFreeq(pTCB->nRcvSegFragQueue);
      pTCB->nRcvSegFragQueue = EMPTY;
      return TCP_OK;
    }
  }

  pqEnq(pTCB->nRcvSegFragQueue,pFragment,-pFragment->n32Seq); /* got one too many */
  return TCP_OK;

alldone:
  if (pFragment != NULL)
    tcpFree((unsigned char *)pFragment);

  pFragment = (TCPFragmentPtr)pqDeq(pTCB->nRcvSegFragQueue);
  while (pFragment != NULL)
  {
    tcpFree((unsigned char *)pFragment);
    pFragment = (TCPFragmentPtr)pqDeq(pTCB->nRcvSegFragQueue);
  }
  pqFreeq(pTCB->nRcvSegFragQueue);
  pTCB->nRcvSegFragQueue = EMPTY;
  pPacket->pTCPSeg->u8Code |= TCP_CODE_FIN;
  return TCP_OK;
}

//**************************************************************************************************
//	tcpIsOK
//	determines, whether a received packet is acceptable
//**************************************************************************************************
static Boolean							// returns TRUE if this is a "good" packet
tcpIsOK(
  TCBPtr 		pTCB,
  PacketPtr	pPacket)
{
  long		n32WindowLast;
  long		n32SegLast;
  int			nDataLen;
  int			nRcvWindow;
  Boolean		bRv;

  if (pTCB->setState < TCP_STATE_SYNRCVD)
    return TRUE;

  nDataLen = netbufLen(pPacket->segBuf) - TCP_HEADER_LEN(pPacket->pTCPSeg);

  // adds SYN and FIN
  if (pPacket->pTCPSeg->u8Code & TCP_CODE_SYN)
    ++nDataLen;
  if (pPacket->pTCPSeg->u8Code & TCP_CODE_FIN)
    ++nDataLen;

  nRcvWindow = pTCB->u32RcvBufSize - pTCB->u32RcvBufCount;

  if ((nRcvWindow == 0) && (nDataLen == 0))
    return (pPacket->pTCPSeg->n32Seq == pTCB->n32RcvNextSeq);

  n32WindowLast = pTCB->n32RcvNextSeq + nRcvWindow - 1;

  bRv = (pPacket->pTCPSeg->n32Seq - pTCB->n32RcvNextSeq) >= 0 &&
        (pPacket->pTCPSeg->n32Seq - n32WindowLast) <= 0;
  if (nDataLen == 0)
    return bRv;
  n32SegLast = pPacket->pTCPSeg->n32Seq + nDataLen - 1;
  bRv |= (n32SegLast - pTCB->n32RcvNextSeq) >= 0 && (n32SegLast - n32WindowLast) <= 0;

  // If no window, strip data but keep ACK, RST and URG
  if (n32WindowLast == 0)
    NETBUF_SETLEN(pPacket->segBuf,TCP_HEADER_LEN(pPacket->pTCPSeg));

  return bRv;
}

//**************************************************************************************************
//	tcpSndMaxSegSize
//	sets sender MSS from option in incoming segment
//**************************************************************************************************
static int
tcpSndMaxSegSize(
  TCBPtr 			pTCB,
  PacketPtr		pPacket,
  unsigned char	*pu8Option)
{
  unsigned uLen,uMSS;

  uLen = *++pu8Option;
  ++pu8Option;						// skips length field

  if ((pPacket->pTCPSeg->u8Code & TCP_CODE_SYN) == 0)
    return uLen;

  switch (uLen-2) 					// subtracts kind & len
  {
  case sizeof(char):
          uMSS = *pu8Option;
    break;
  case sizeof(short):
          uMSS = ntohs(*(unsigned short*)pu8Option);
    break;
  case sizeof(long):
          uMSS = ntohl(*(unsigned long*)pu8Option);
    break;
  default:
    uMSS = pTCB->u32SndMaxSegSize;
    break;
  }

  uMSS -= TCP_MIN_HDR_LEN;			// save just the data buffer size

  if (pTCB->u32SndMaxSegSize != 0)
    pTCB->u32SndMaxSegSize = MIN(uMSS,pTCB->u32SndMaxSegSize);
  else
    pTCB->u32SndMaxSegSize = uMSS;

  return uLen;
}

//**************************************************************************************************
//	tcpOptions
//	handles TCP options for an inbound segment
//**************************************************************************************************
static int
tcpOptions(
  TCBPtr 		pTCB,
  PacketPtr	pPacket)
{
  unsigned char	*pu8Options;
  unsigned char	*pu8OptionEnd;
  unsigned		uLen;
  unsigned		uToCopy;
  int				nSrcOffset;
  int				nDstPos;

  if (TCP_HEADER_LEN(pPacket->pTCPSeg) == TCP_MIN_HDR_LEN)
    return TCP_OK;

  pu8Options = pPacket->pTCPSeg->aryData;
  pu8OptionEnd = (unsigned char*)pPacket->pTCPSeg + TCP_HEADER_LEN(pPacket->pTCPSeg);

  do
  {
    switch(*pu8Options)
    {
    case TCP_OPTION_NOOP:
      pu8Options++;				// falls through
    case TCP_OPTION_EOL:
      break;
    case TCP_OPTION_MAXSEGSIZE:
      pu8Options += tcpSndMaxSegSize(pTCB,pPacket,pu8Options);
      break;
    default:
      pu8Options++;				// skips option code
      if (*pu8Options > 0 && *pu8Options <= pu8OptionEnd - pu8Options - 1)
        pu8Options += *pu8Options - 1;
      else
        pu8Options = pu8OptionEnd;
      break;
    }
  }
  while (*pu8Options != TCP_OPTION_EOL && pu8Options < pu8OptionEnd);

  // deletes the options
  uLen = netbufLen(pPacket->segBuf) - TCP_HEADER_LEN(pPacket->pTCPSeg);

  nSrcOffset = TCP_HEADER_LEN(pPacket->pTCPSeg) - TCP_MIN_HDR_LEN;
  nDstPos = 0;
  uToCopy = uLen;
  while (uToCopy-- > 0)
  {
    pPacket->pTCPSeg->aryData[nDstPos++] = pPacket->pTCPSeg->aryData[nSrcOffset++];
  }

  NETBUF_SETLEN(pPacket->segBuf,TCP_MIN_HDR_LEN + uLen);
  pPacket->pTCPSeg->u8Offset = TCP_HDR_OFFSET;

  return TCP_OK;
}

//**************************************************************************************************
//	tcpreset
//	generates a reset in response to a bad packet
//**************************************************************************************************
static int
tcpReset(
  PacketPtr	pPacket)
{
  TCPSegPtr		pTCPOutSeg;
  NetBuf			buf;
  NetAttrs		attrs;
  unsigned short	u16Port;
  IPAddressUon	uonSrcIpAddr;
  IPAddressUon	uonDstIpAddr;
  int				nDataLen;

  if (pPacket->pTCPSeg->u8Code & TCP_CODE_RST)
    return TCP_OK;

  if (netbufAlloc(&buf,TCP_MIN_HDR_LEN+MAX_ETHER_IP_HEADER_LEN) == 0)
    return TCP_GENERAL_ERROR;
  buf->start += MAX_ETHER_IP_HEADER_LEN;

  if (netattrNew(&attrs) == 0)
    return TCP_GENERAL_ERROR;

  if (tcpGetIPAddr(pPacket->netAttrs,TCP_IP_SRC_ADDR,&uonSrcIpAddr) != TCP_OK)
    return TCP_GENERAL_ERROR;
  if (tcpGetIPAddr(pPacket->netAttrs,TCP_IP_DST_ADDR,&uonDstIpAddr) != TCP_OK)
    return TCP_GENERAL_ERROR;
  if (tcpSetIPAddr(attrs,TCP_IP_SRC_ADDR,&uonDstIpAddr) != TCP_OK)
    return TCP_GENERAL_ERROR;
  if (tcpSetIPAddr(attrs,TCP_IP_DST_ADDR,&uonSrcIpAddr) != TCP_OK)
    return TCP_GENERAL_ERROR;

  pTCPOutSeg = (TCPSegPtr)NETBUF_DATA(buf);

  if (!netattrGet(pPacket->netAttrs,NETATTR_TCP_TO,&u16Port))
    return NULL;
  if (!netattrSet(attrs,NETATTR_TCP_FROM,u16Port))
    return NULL;
  pTCPOutSeg->u16SrcPort = pPacket->pTCPSeg->u16DstPort;

  if (!netattrGet(pPacket->netAttrs,NETATTR_TCP_FROM,&u16Port))
    return NULL;
  if (!netattrSet(attrs,NETATTR_TCP_TO,u16Port))
    return NULL;
  pTCPOutSeg->u16DstPort = pPacket->pTCPSeg->u16SrcPort;

  if (pPacket->pTCPSeg->u8Code & TCP_CODE_ACK)
  {
    pTCPOutSeg->n32Seq = pPacket->pTCPSeg->n32AckSeq;
    pTCPOutSeg->u8Code = TCP_CODE_RST;
  }
  else
  {
    pTCPOutSeg->n32Seq = 0;
    pTCPOutSeg->u8Code = TCP_CODE_RST | TCP_CODE_ACK;
  }

  nDataLen = netbufLen(pPacket->segBuf) - TCP_HEADER_LEN(pPacket->pTCPSeg);

  if (pPacket->pTCPSeg->u8Code & TCP_CODE_SYN)
    nDataLen++;
  if (pPacket->pTCPSeg->u8Code & TCP_CODE_FIN)
    nDataLen++;

  pTCPOutSeg->n32AckSeq = pPacket->pTCPSeg->n32Seq + nDataLen;
  pTCPOutSeg->u8Offset = TCP_HDR_OFFSET;
  pTCPOutSeg->u16Window = 0;
  pTCPOutSeg->u16Urgent = 0;

  tcpHeaderHost2Net(pTCPOutSeg);

  pTCPOutSeg->u16CheckSum = 0;
  NETBUF_SETLEN(buf,TCP_MIN_HDR_LEN);
  pTCPOutSeg->u16CheckSum = tcpCheckSum(buf,attrs,TCP_MIN_HDR_LEN);

  gu32TcpOutSegs++;
  gu32TcpOutRsts++;

  if (!netattrSet(attrs,NETATTR_IP_PROTOCOL,NETIP_PROTOCOL_TCP))
    return NULL;

  tcpPacketDump(pTCPOutSeg,"SR",TCP_MIN_HDR_LEN);
  netcfgSendNextDown(NETMODULE_TCP_OUT,buf,attrs);
  return TCP_OK;
}

//**************************************************************************************************
//	tcpkick
//	makes sure we send a packet soon
//**************************************************************************************************
int
tcpKick(
  TCBPtr pTCB)
{
  if (pTCB->u16Flags & TCP_TCB_DELACK && !tcp_tmLeft(&gOutputPort,pTCB,SEND))
  {
    tcp_tmSet(&gOutputPort,TCP_QUEUE_LEN,pTCB,SEND,TCP_ACKDELAY);
  }
  else if (ptCount(&gOutputPort) < TCP_QUEUE_LEN)
  {
    ptSend(&gOutputPort,pTCB,SEND);
  }
  return TCP_OK;
}

//**************************************************************************************************
//	tcpSndWindow
//	handlea send window updates from remote
//**************************************************************************************************
static int
tcpSndWindow(
  TCBPtr		pTCB,
  PacketPtr	pPacket)
{
  long n32WinLast;
  long n32OutWinLast;

  if (COMPARE_SEQUENCE(pPacket->pTCPSeg->n32Seq,pTCB->n32SeqOfLastWinUpdt) < 0)
    return TCP_OK;

  if (COMPARE_SEQUENCE(pPacket->pTCPSeg->n32Seq,pTCB->n32SeqOfLastWinUpdt) == 0 &&
      COMPARE_SEQUENCE(pPacket->pTCPSeg->n32AckSeq,pTCB->n32AckSeqOfLastWinUpdt) < 0)
    return TCP_OK;

  // else, we have a send window updat
  // computes the last sequences of the new and old windows

  n32OutWinLast = pTCB->n32AckSeqOfLastWinUpdt + pTCB->u32SndWinSize;
  n32WinLast = pPacket->pTCPSeg->n32AckSeq + pPacket->pTCPSeg->u16Window;
  pTCB->u32SndWinSize = pPacket->pTCPSeg->u16Window;
  pTCB->n32SeqOfLastWinUpdt = pPacket->pTCPSeg->n32Seq;
  pTCB->n32AckSeqOfLastWinUpdt = pPacket->pTCPSeg->n32AckSeq;

  if (COMPARE_SEQUENCE(n32WinLast,n32OutWinLast) <= 0)
    return TCP_OK;

  // else,  window increased
  if (pTCB->setOutState == TCP_OUT_STATE_PERSISTS)
  {
    tcp_tmClear(&gOutputPort,pTCB,PERSIST);
    pTCB->setOutState = TCP_OUT_STATE_TRANSMIT;
  }

  tcpKick(pTCB);

  return TCP_OK;
}

//**************************************************************************************************
//	tcpRcvWindow
//	does receive window processing for a TCB
//**************************************************************************************************
int
tcpRcvWindow(
  TCBPtr 	pTCB)
{
  int nWindow = 0;

  nWindow = pTCB->u32RcvBufSize - pTCB->u32RcvBufCount;

  if (pTCB->setState < TCP_STATE_ESTABLISHED)
    return nWindow;

  // Receiver-Side Silly Window Syndrome Avoidance: Never shrink an already-advertised window, but
  // semWait for at least 1/4 receiver buffer and 1 max-sized segment before opening a zero window.
  if (nWindow*4 < (int)pTCB->u32RcvBufSize || nWindow < (int)pTCB->u32RcvMaxSegSize)
    nWindow = 0;
  nWindow = MAX(nWindow,pTCB->n32CurAdvWinSeq -  pTCB->n32RcvNextSeq);
  pTCB->n32CurAdvWinSeq = pTCB->n32RcvNextSeq + nWindow;
  return nWindow;
}

//**************************************************************************************************
//	tcpwakeup
//	wakes up processes sleeping for TCP
//**************************************************************************************************
int
tcpWakeup(
  TCBPtr 	pTCB,
  int		nType)
{
  int nFreeLen = 0;

  if (nType & TCP_READERS)
  {
    if (((pTCB->u16Flags & TCP_TCB_RCV_DONE) || pTCB->u32RcvBufCount > 0 ||
         (pTCB->u16Flags & TCP_TCB_RCV_URGENT_ISOK)))
    {
      semSignalIfLess(&pTCB->recRcvSemaphore,1);
    }
  }

  if (nType & TCP_WRITERS)
  {
    nFreeLen = pTCB->u32SndBufSize - pTCB->u32SndBufCount;

    if ((pTCB->u16Flags & TCP_TCB_SND_DONE) || nFreeLen > 0)
    {
      semSignalIfLess(&pTCB->recSndSemaphore,1);
    }

    // special for abort
    if (pTCB->nError && pTCB->recOpenCloseSemaphore.nsCount > 0)
      semSignal(&pTCB->recOpenCloseSemaphore);
  }

  return TCP_OK;
}

//**************************************************************************************************
//	tcpAbort
//	aborts an active TCP connection
//**************************************************************************************************
static int
tcpAbort(
  TCBPtr	pTCB,
  int		nError)
{
  tcpKillTimers(pTCB);
  pTCB->u16Flags |= TCP_TCB_RCV_DONE|TCP_TCB_SND_DONE;
  pTCB->nError = nError;
  tcpWakeup(pTCB,TCP_READERS|TCP_WRITERS);
  return TCP_OK;
}

//**************************************************************************************************
//	tcpHowMuch
//	computes how much data is available to send
//**************************************************************************************************
static int
tcpHowMuch(
  TCBPtr	pTCB)
{
  int nToSend = 0;

  nToSend = pTCB->n32SndUnacked + pTCB->u32SndBufCount - pTCB->n32SndNext;
  if (pTCB->u8NextPacketCode & TCP_CODE_SYN)
    ++nToSend;
  if (pTCB->u16Flags & TCP_TCB_SEND_FIN)
    ++nToSend;
  return nToSend;
}

//**************************************************************************************************
//	tcpSendLen
//	computes the packet length and offset in sndbuf
//**************************************************************************************************
static int
tcpSendLen(
  TCBPtr			pTCB,
  Boolean			bRetransmit,
  unsigned int 	*puOffset)
{
  unsigned	uDataLen;

  if (bRetransmit || (pTCB->u8NextPacketCode & TCP_CODE_SYN))
    *puOffset = 0;
  else
    *puOffset = pTCB->n32SndNext - pTCB->n32SndUnacked;

  uDataLen = pTCB->u32SndBufCount - *puOffset;
  uDataLen = MIN(uDataLen,pTCB->u32SndWinSize);
  return MIN(uDataLen,pTCB->u32SndMaxSegSize);
}

//**************************************************************************************************
//	tcpRcvMaxSegSize
//	sets receive MSS option (MSS = Max Segment Size)
//**************************************************************************************************
static void
tcpRcvMaxSegSize(
  TCBPtr		pTCB,
  TCPSegPtr	pTCPSeg)
{
  int	nHdrLen = 0;
  int nOptionLen = 0;
  int nMSS = 0;
  int n = 0;

  nHdrLen = TCP_HEADER_LEN(pTCPSeg);
  nOptionLen = 2 + sizeof(short);
  pTCPSeg->aryData[nHdrLen - TCP_MIN_HDR_LEN] = TCP_OPTION_MAXSEGSIZE;	// option kind
  pTCPSeg->aryData[nHdrLen - TCP_MIN_HDR_LEN + 1] = nOptionLen;			// option length
  nMSS = pTCB->u32RcvMaxSegSize;

  for (n = nOptionLen-1;n > 1;n--)
  {
    pTCPSeg->aryData[nHdrLen - TCP_MIN_HDR_LEN + n] = nMSS & 0xFF;
    nMSS >>= 8;
  }

  nHdrLen += nOptionLen + 3;						// +3 for proper rounding below

  // header length is high 4 bits of tcp_offset, in longs
  pTCPSeg->u8Offset = ((nHdrLen << 2) & 0xF0) | pTCPSeg->u8Offset & 0x0F;
}

//**************************************************************************************************
//	tcpSend
//	computes and sends a TCP segment for the given TCB
//**************************************************************************************************
static int
tcpSend(
  TCBPtr	pTCB,
  Boolean	bRetransmit)
{
  long			n32Left,n32Copied;
  unsigned		uDataLen;
  unsigned		uOffset;
  int				nNewData;
  int				nCurPos;
  NetBuf			buf;
  NetAttrs		attrs;
  unsigned short	u16Port;
  TCPSegPtr		pTCPSeg;
  short			n16Urgent;
  IPAddressUon	uonIpAddress;

  if (!netattrNew(&attrs))
    return TCP_GENERAL_ERROR;

  if (tcpGetIPAddr(pTCB->netParams,TCP_IP_SRC_ADDR,&uonIpAddress) != TCP_OK)
    return TCP_GENERAL_ERROR;
  if (tcpSetIPAddr(attrs,TCP_IP_SRC_ADDR,&uonIpAddress) != TCP_OK)
    return TCP_GENERAL_ERROR;

  if (tcpGetIPAddr(pTCB->netParams,TCP_IP_DST_ADDR,&uonIpAddress) != TCP_OK)
    return TCP_GENERAL_ERROR;
  if (tcpSetIPAddr(attrs,TCP_IP_DST_ADDR,&uonIpAddress) != TCP_OK)
    return TCP_GENERAL_ERROR;

  uDataLen = tcpSendLen(pTCB,bRetransmit,&uOffset);

  if (!netbufAlloc(&buf,uDataLen + TCP_MAX_HDR_LEN + MAX_ETHER_IP_HEADER_LEN))
    return TCP_GENERAL_ERROR;
  buf->start += MAX_ETHER_IP_HEADER_LEN;

  pTCPSeg = (TCPSegPtr)NETBUF_DATA(buf);

  if (!netattrGet(pTCB->netParams,NETATTR_TCP_FROM,&u16Port))
    return NULL;
  if (!netattrSet(attrs,NETATTR_TCP_FROM,u16Port))
    return NULL;
  pTCPSeg->u16SrcPort = u16Port;

  if (!netattrGet(pTCB->netParams,NETATTR_TCP_TO,&u16Port))
    return NULL;
  if (!netattrSet(attrs,NETATTR_TCP_TO,u16Port))
    return NULL;
  pTCPSeg->u16DstPort = u16Port;

  if (bRetransmit)
    pTCPSeg->n32Seq = pTCB->n32SndUnacked;
  else
    pTCPSeg->n32Seq = pTCB->n32SndNext;

  pTCPSeg->n32AckSeq = pTCB->n32RcvNextSeq;

  if ((pTCB->u16Flags & TCP_TCB_SEND_FIN) &&
      COMPARE_SEQUENCE(pTCPSeg->n32Seq + uDataLen,pTCB->n32SndLast) == 0)
    pTCB->u8NextPacketCode |= TCP_CODE_FIN;

  pTCPSeg->u8Code = pTCB->u8NextPacketCode;
  pTCPSeg->u8Offset = TCP_HDR_OFFSET;
  if ((pTCB->u16Flags & TCP_TCB_FIRST_SEND) == 0)
    pTCPSeg->u8Code |= TCP_CODE_ACK;
  if (pTCPSeg->u8Code & TCP_CODE_SYN)
    tcpRcvMaxSegSize(pTCB,pTCPSeg);
  if (uDataLen > 0)
    pTCPSeg->u8Code |= TCP_CODE_PSH;
  pTCPSeg->u16Window = tcpRcvWindow(pTCB);

  if (pTCB->u16Flags & TCP_TCB_SND_URGENT_ISOK)
  {
    n16Urgent = pTCB->n32SndUrgentSeq - pTCPSeg->n32Seq;
    if (n16Urgent >= 0)
    {
#ifdef	BSDURG
      pTCPSeg->u16Urgent = n16Urgent + 1;
#else

      pTCPSeg->u16Urgent = n16Urgent;
#endif

      pTCPSeg->u8Code |= TCP_CODE_URG;
    }
    else
      pTCPSeg->u16Urgent = 0;
  }
  else
    pTCPSeg->u16Urgent = 0;

  // copies data from snd buffer to the tcp segment
  nCurPos = (pTCB->u32SndBufStart + uOffset) % pTCB->u32SndBufSize;
  if (uDataLen > 0)
  {
    n32Left = pTCB->u32SndBufSize - nCurPos;
    if (uDataLen > n32Left)
    {
      n32Copied = netbufWrite(buf,&pTCB->pu8sndBuf[nCurPos],n32Left,TCP_HEADER_LEN(pTCPSeg));
      if (n32Copied != n32Left)
        return TCP_GENERAL_ERROR;
      n32Copied = netbufWrite(buf,&pTCB->pu8sndBuf[0],uDataLen - n32Left,
                              TCP_HEADER_LEN(pTCPSeg) + n32Left);
      if (n32Copied != (uDataLen - n32Left))
        return TCP_GENERAL_ERROR;
    }
    else
    {
      n32Copied = netbufWrite(buf,&pTCB->pu8sndBuf[nCurPos],uDataLen,TCP_HEADER_LEN(pTCPSeg));
      if (n32Copied != uDataLen)
        return TCP_GENERAL_ERROR;
    }
  }

  pTCB->u16Flags &= ~TCP_TCB_NEED_OUTPUT;					// we're doing it

  if (bRetransmit)
  {
    nNewData = pTCB->n32SndUnacked + uDataLen - pTCB->n32SndNext;
    if (nNewData < 0)
      nNewData = 0;
    gu32TcpRetransSegs++;
  }
  else
  {
    nNewData = uDataLen;
    if (pTCB->u8NextPacketCode & TCP_CODE_SYN)
      nNewData++; 	// SYN is part of the sequence
    if (pTCB->u8NextPacketCode & TCP_CODE_FIN)
      nNewData++; 	// FIN is part of the sequence
  }

  pTCB->n32SndNext += nNewData;
  if (nNewData >= 0)
    gu32TcpOutSegs++;

  if (pTCB->setState == TCP_STATE_TIMEWAIT)
    tcpWait(pTCB);	// final ACK

  uDataLen += TCP_HEADER_LEN(pTCPSeg);

  tcpHeaderHost2Net(pTCPSeg);

  NETBUF_SETLEN(buf,uDataLen);

  pTCPSeg->u16CheckSum = 0;
  pTCPSeg->u16CheckSum = tcpCheckSum(buf,attrs,uDataLen);

  if (!netattrSet(attrs,NETATTR_IP_PROTOCOL,NETIP_PROTOCOL_TCP))
    return NULL;

  tcpPacketDump(pTCPSeg,"SN",uDataLen);
  netcfgSendNextDown(NETMODULE_TCP_OUT,buf,attrs);
  return TCP_OK;
}

//**************************************************************************************************
//	tcpReTransmit
//	handles TCP output events while we are retransmitting
//**************************************************************************************************
static int
tcpReTransmit(
  TCBPtr 			pTCB,
  unsigned long	u32Event)
{
  int	nTimeout;

  if (u32Event != RETRANSMIT)
    return TCP_OK;	// ignore others while retransmitting

  if (++pTCB->nNumOfRetransmits > TCP_MAXRETRIES)
  {
    tcpAbort(pTCB,TCP_TIMEDOUT);
    return TCP_OK;
  }

  tcpSend(pTCB,TCP_REXMT);

  nTimeout = MIN(pTCB->nRetransmitTimeout << pTCB->nNumOfRetransmits,TCP_MAXRXT);
  tcp_tmSet(&gOutputPort,TCP_QUEUE_LEN,pTCB,RETRANSMIT,nTimeout);

  if (pTCB->setOutState != TCP_OUT_STATE_RETRANSMIT)
    pTCB->u32SlowStartThreshold = pTCB->u32CongestionWinSize;

  pTCB->u32SlowStartThreshold = MIN(pTCB->u32SndWinSize,pTCB->u32SlowStartThreshold)/2;

  if (pTCB->u32SlowStartThreshold < pTCB->u32SndMaxSegSize)
    pTCB->u32SlowStartThreshold = pTCB->u32SndMaxSegSize;

  pTCB->u32CongestionWinSize = pTCB->u32SndMaxSegSize;

  return TCP_OK;
}

//**************************************************************************************************
//	tcpTransmit
//	handles TCP output events while we are transmitting
//**************************************************************************************************
static int
tcpTransmit(
  TCBPtr 			pTCB,
  unsigned long	u32Event)
{
  int	nToSend = 0;
  int nWindow = 0;
  int	nPending = 0;

  if (u32Event == RETRANSMIT)
  {
    tcp_tmClear(&gOutputPort,pTCB,SEND);
    tcpReTransmit(pTCB,u32Event);
    pTCB->setOutState = TCP_OUT_STATE_RETRANSMIT;
    return TCP_OK;
  } // else SEND

  nToSend = tcpHowMuch(pTCB);
  if (nToSend == 0)
  {
    if (pTCB->u16Flags & TCP_TCB_NEED_OUTPUT)
      tcpSend(pTCB,TCP_NEWDATA);
    if (pTCB->n32SndNext == pTCB->n32SndUnacked)
      return TCP_OK;

    // still unacked data; restarts transmit timer
    if (!tcp_tmLeft(&gOutputPort,pTCB,RETRANSMIT))
      tcp_tmSet(&gOutputPort,TCP_QUEUE_LEN,pTCB,RETRANSMIT,pTCB->nRetransmitTimeout);

    return TCP_OK;
  }
  else if (pTCB->u32SndWinSize == 0)
  {
    pTCB->setOutState = TCP_OUT_STATE_PERSISTS;
    pTCB->nPersist = pTCB->nRetransmitTimeout;
    tcpSend(pTCB,TCP_NEWDATA);
    tcp_tmSet(&gOutputPort,TCP_QUEUE_LEN,pTCB,PERSIST,pTCB->nPersist);
    return TCP_OK;
  }	// else, we have data and window

  pTCB->setOutState = TCP_OUT_STATE_TRANSMIT;
  nWindow = MIN(pTCB->u32SndWinSize,pTCB->u32CongestionWinSize);
  nPending = pTCB->n32SndNext - pTCB->n32SndUnacked;
  while ((tcpHowMuch(pTCB) > 0) && (nPending < nWindow))
  {
    tcpSend(pTCB,TCP_NEWDATA);
    nPending = pTCB->n32SndNext - pTCB->n32SndUnacked;
  }
  if (!tcp_tmLeft(&gOutputPort,pTCB,RETRANSMIT))
  {
    tcp_tmSet(&gOutputPort,TCP_QUEUE_LEN,pTCB,RETRANSMIT,pTCB->nRetransmitTimeout);
  }

  return TCP_OK;
}

//**************************************************************************************************
//	tcpIdle
//	handles events while a connection is idle
//**************************************************************************************************
static int
tcpIdle(
  TCBPtr 			pTCB,
  unsigned long	u32Event)
{
  if (u32Event == SEND)
    tcpTransmit(pTCB,u32Event);
  return TCP_OK;
}

//**************************************************************************************************
//	tcpPersist
//	handles events while the send window is closed
//**************************************************************************************************
static int
tcpPersist(
  TCBPtr 			pTCB,
  unsigned long	u32Event)
{
  if (u32Event != PERSIST && u32Event != SEND)
    return TCP_OK;
  tcpSend(pTCB,TCP_REXMT);
  pTCB->nPersist = MIN(pTCB->nPersist << 1,TCP_MAXPRS);
  tcp_tmSet(&gOutputPort,TCP_QUEUE_LEN,pTCB,PERSIST,pTCB->nPersist);
  return TCP_OK;
}

//**************************************************************************************************
//	tcpOutDispatcher
//	handles events affecting TCP output processing
//**************************************************************************************************
static void
tcpOutDispatcher(
  TCBPtr 			pTCB,
  unsigned long	u32Event)
{
  switch(pTCB->setOutState)
  {
  case TCP_OUT_STATE_IDLE:
    tcpIdle(pTCB,u32Event);
    break;
  case TCP_OUT_STATE_PERSISTS:
    tcpPersist(pTCB,u32Event);
    break;
  case TCP_OUT_STATE_TRANSMIT:
    tcpTransmit(pTCB,u32Event);
    break;
  case TCP_OUT_STATE_RETRANSMIT:
    tcpReTransmit(pTCB,u32Event);
    break;
  default:
    break;
  }
}

//**************************************************************************************************
//	tcpOut
//	handles events affecting TCP output processing
//**************************************************************************************************
#ifdef MacOS
pascal void *tcpOut(ThreadArg arg)
#else
void tcpOut(ThreadArg arg)
#endif
{
  ThreadId		myThreadId,parentThreadId;
  TCBPtr			pTCB;
  unsigned long	u32Event;

  tmGetInfo(SELF,&myThreadId,&parentThreadId);

  // Add to list of modules
  netmodAdd(NETMODULE_TCP_OUT, myThreadId);

  netdbgPrintf(NETDEBUG_MODULES,"tcp module started (moduleId=%d, threadId=%d).\n",
               NETMODULE_TCP_OUT, myThreadId);

  ptInit(&gOutputPort,TCP_QUEUE_LEN);

  while (TRUE)
  {
    ptReceive(&gOutputPort,&pTCB,&u32Event);

    if (pTCB->setState <= TCP_STATE_CLOSED)
      continue;
    semWait(&pTCB->recMutex);
    if (pTCB->setState <= TCP_STATE_CLOSED)
      continue;

    if (u32Event == DELETE)
      tcpTCBDeallocate(pTCB);
    else
    {
      tcpOutDispatcher(pTCB,u32Event);
      semSignal(&pTCB->recMutex);
    }
  }
}

//**************************************************************************************************
//	tcpOutputState
//	does TCP output state processing after an ACK
//**************************************************************************************************
static int
tcpOutputState(
  TCBPtr	pTCB,
  int 	nAcked)
{
  if (nAcked <= 0)
    return TCP_OK;				// no state change

  if (pTCB->setOutState == TCP_OUT_STATE_RETRANSMIT)
  {
    pTCB->nNumOfRetransmits = 0;
    pTCB->setOutState = TCP_OUT_STATE_TRANSMIT;
  }

  if (pTCB->u32SndBufCount == 0)
  {
    pTCB->setOutState = TCP_OUT_STATE_IDLE;
    return TCP_OK;
  }

  tcpKick(pTCB);
  return TCP_OK;
}

//**************************************************************************************************
//	tcpAcked
//	handles in-bound ACKs and do round trip estimates
//**************************************************************************************************
static int
tcpAcked(
  TCBPtr		pTCB,
  PacketPtr	pPacket)
{
  long			n32Acked;
  long			n32NumOfAcked;

  if (!(pPacket->pTCPSeg->u8Code & TCP_CODE_ACK))
    return TCP_GENERAL_ERROR;

  n32Acked = pPacket->pTCPSeg->n32AckSeq - pTCB->n32SndUnacked;
  n32NumOfAcked = 0;
  if (n32Acked <= 0)
    return 0;
  if (COMPARE_SEQUENCE(pPacket->pTCPSeg->n32AckSeq,pTCB->n32SndNext) > 0)
    if (pTCB->setState == TCP_STATE_SYNRCVD)
      return tcpReset(pPacket);
    else
      return tcpAckIt(pTCB,pPacket);

  tcpRoundTripTime(pTCB);

  pTCB->n32SndUnacked = pPacket->pTCPSeg->n32AckSeq;

  if (n32Acked && pTCB->u8NextPacketCode & TCP_CODE_SYN)
  {
    n32Acked--;
    n32NumOfAcked++;
    pTCB->u8NextPacketCode &= ~TCP_CODE_SYN;
    pTCB->u16Flags &= ~TCP_TCB_FIRST_SEND;
  }

  if (	(pTCB->u8NextPacketCode & TCP_CODE_FIN) &&
       COMPARE_SEQUENCE(pPacket->pTCPSeg->n32AckSeq,pTCB->n32SndNext) == 0)
  {
    n32Acked--;
    n32NumOfAcked++;
    pTCB->u8NextPacketCode &= ~TCP_CODE_FIN;
    pTCB->u16Flags &= ~TCP_TCB_SEND_FIN;
  }

  if (	(pTCB->u16Flags & TCP_TCB_SND_URGENT_ISOK) &&
       COMPARE_SEQUENCE(pPacket->pTCPSeg->n32AckSeq,pTCB->n32SndUrgentSeq) >= 0)
    pTCB->u16Flags &= ~TCP_TCB_SND_URGENT_ISOK;

  pTCB->u32SndBufStart = (pTCB->u32SndBufStart+n32Acked) % pTCB->u32SndBufSize;
  pTCB->u32SndBufCount -= n32Acked;

  if (n32Acked)
    semSignalIfLess(&pTCB->recSndSemaphore,1);

  tcpOutputState(pTCB,n32Acked+n32NumOfAcked);

  return n32Acked;
}

//**************************************************************************************************
//	tcpAckIt
//**************************************************************************************************
static int
tcpAckIt(
  TCBPtr		pTCB,
  PacketPtr	pPacket)
{
  TCPSegPtr		pTCPOutSeg;
  NetBuf			buf;
  NetAttrs		attrs;
  unsigned short	u16Port;
  IPAddressUon	uonSrcIpAddr;
  IPAddressUon	uonDstIpAddr;

  if (pPacket->pTCPSeg->u8Code & TCP_CODE_RST)
    return TCP_OK;

  if ( (netbufLen(pPacket->segBuf) <= TCP_HEADER_LEN(pPacket->pTCPSeg)) &&
       !(pPacket->pTCPSeg->u8Code & (TCP_CODE_SYN | TCP_CODE_FIN)))
    return TCP_OK;

  if (!netbufAlloc(&buf,TCP_MIN_HDR_LEN + MAX_ETHER_IP_HEADER_LEN))
    return TCP_GENERAL_ERROR;
  buf->start += MAX_ETHER_IP_HEADER_LEN;

  if (!netattrNew(&attrs))
    return TCP_GENERAL_ERROR;

  if (tcpGetIPAddr(pPacket->netAttrs,TCP_IP_SRC_ADDR,&uonSrcIpAddr) != TCP_OK)
    return TCP_GENERAL_ERROR;
  if (tcpGetIPAddr(pPacket->netAttrs,TCP_IP_DST_ADDR,&uonDstIpAddr) != TCP_OK)
    return TCP_GENERAL_ERROR;
  if (tcpSetIPAddr(attrs,TCP_IP_SRC_ADDR,&uonDstIpAddr) != TCP_OK)
    return TCP_GENERAL_ERROR;
  if (tcpSetIPAddr(attrs,TCP_IP_DST_ADDR,&uonSrcIpAddr) != TCP_OK)
    return TCP_GENERAL_ERROR;

  pTCPOutSeg = (TCPSegPtr)NETBUF_DATA(buf);

  if (!netattrGet(pPacket->netAttrs,NETATTR_TCP_TO,&u16Port))
    return TCP_GENERAL_ERROR;
  if (!netattrSet(attrs,NETATTR_TCP_FROM,u16Port))
    return TCP_GENERAL_ERROR;
  pTCPOutSeg->u16SrcPort = pPacket->pTCPSeg->u16DstPort;

  if (!netattrGet(pPacket->netAttrs,NETATTR_TCP_FROM,&u16Port))
    return NULL;
  if (!netattrSet(attrs,NETATTR_TCP_TO,u16Port))
    return NULL;

  pTCPOutSeg->u16DstPort = pPacket->pTCPSeg->u16SrcPort;
  pTCPOutSeg->n32Seq = pTCB->n32SndNext;
  pTCPOutSeg->n32AckSeq = pTCB->n32RcvNextSeq;
  pTCPOutSeg->u8Code = TCP_CODE_ACK;
  pTCPOutSeg->u8Offset = TCP_HDR_OFFSET;
  pTCPOutSeg->u16Window = tcpRcvWindow(pTCB);
  pTCPOutSeg->u16Urgent = 0;

  tcpHeaderHost2Net(pTCPOutSeg);
  NETBUF_SETLEN(buf,TCP_MIN_HDR_LEN);

  pTCPOutSeg->u16CheckSum = 0;
  pTCPOutSeg->u16CheckSum = tcpCheckSum(buf,attrs,TCP_MIN_HDR_LEN);

  gu32TcpOutSegs++;

  if (!netattrSet(attrs,NETATTR_IP_PROTOCOL,NETIP_PROTOCOL_TCP))
    return NULL;

  tcpPacketDump(pTCPOutSeg,"SA",TCP_MIN_HDR_LEN);
  netcfgSendNextDown(NETMODULE_TCP_OUT,buf,attrs);
  return TCP_OK;
}

//**************************************************************************************************
//	tcpClosed
//**************************************************************************************************
static int
tcpClosed(
  TCBPtr 		pTCB,
  PacketPtr	pPacket)
{
  tcpReset(pPacket);
  return TCP_GENERAL_ERROR;
}

//**************************************************************************************************
//	tcpWindowInit
//	���
//**************************************************************************************************
static int
tcpWindowInit(
  TCBPtr 		pTCB,
  TCBPtr 		pNewTCB,
  PacketPtr	pPacket)
{
  unsigned long u32MaxSegSize;

  pNewTCB->u32SndWinSize = pPacket->pTCPSeg->u16Window;
  pNewTCB->n32SeqOfLastWinUpdt = pPacket->pTCPSeg->n32Seq;
  pNewTCB->n32AckSeqOfLastWinUpdt = pNewTCB->n32InitSndSeq;

  u32MaxSegSize = 536;
  pNewTCB->u32SndMaxSegSize = u32MaxSegSize;
  pNewTCB->u32RcvMaxSegSize = u32MaxSegSize;
  pNewTCB->u32CongestionWinSize = pNewTCB->u32SndMaxSegSize;
  pNewTCB->u32SlowStartThreshold = 65535;	// ip max window
  pNewTCB->n32RcvNextSeq = pPacket->pTCPSeg->n32Seq;
  pNewTCB->n32CurAdvWinSeq = pNewTCB->n32RcvNextSeq + pNewTCB->u32RcvBufSize;
  return 1;
}

//**************************************************************************************************
//	tcpdodat
//	does input data processing
//**************************************************************************************************
static int
tcpDoData(
  TCBPtr 		pTCB,
  PacketPtr	pPacket,
  long		n32First,
  unsigned	uDataLen)
{
  int	nWakeup = 0;

  if (pTCB->n32RcvNextSeq == n32First)
  {
    if (uDataLen > 0)
    {
      tcpFragmentJoin(pTCB,pPacket,uDataLen);
      pTCB->u16Flags |= TCP_TCB_NEED_OUTPUT;
      nWakeup++;
    }

    if (pPacket->pTCPSeg->u8Code & TCP_CODE_FIN)
    {
      pTCB->u16Flags |= TCP_TCB_RCV_DONE | TCP_TCB_NEED_OUTPUT;
      pTCB->n32RcvNextSeq++;
      nWakeup++;
    }

    if (pPacket->pTCPSeg->u8Code & (TCP_CODE_PSH | TCP_CODE_URG))
    {
      pTCB->u16Flags |= TCP_TCB_PUSH;
      nWakeup++;
    }

    if (nWakeup != 0)
      tcpWakeup(pTCB,TCP_READERS);
  }
  else
  {
    // process delayed controls
    if (pPacket->pTCPSeg->u8Code & TCP_CODE_FIN)
      pTCB->n32FINSeqNum = pPacket->pTCPSeg->n32Seq + uDataLen;

    if (pPacket->pTCPSeg->u8Code & (TCP_CODE_PSH | TCP_CODE_URG))
      pTCB->n32PUSHSeqNum = pPacket->pTCPSeg->n32Seq + uDataLen;

    pPacket->pTCPSeg->u8Code &= ~(TCP_CODE_FIN | TCP_CODE_PSH);
    tcpFragmentInsert(pTCB,n32First,uDataLen);
  }

  return TCP_OK;
}

//**************************************************************************************************
//	tcpData
//	process an input segment's data section
//**************************************************************************************************
static int
tcpData(
  TCBPtr 		pTCB,
  PacketPtr	pPacket)
{
  int 		nDataLen = 0;
  int 		nRcvWin = 0;
  long		n32WinLast = 0;
  long		n32First = 0,n32Last = 0;
  unsigned	uRcvBufPos = 0;
  int		nSegBufPos = 0;
  long		n32Left = 0,n32Copied = 0;

  if (pPacket->pTCPSeg->u8Code & TCP_CODE_URG)
  {
    int nRcvUrgent = pPacket->pTCPSeg->n32Seq + pPacket->pTCPSeg->u16Urgent;

#ifdef BSDURG

    nRcvUrgent--;
#endif

    if (!(pTCB->u16Flags & TCP_TCB_RCV_URGENT_ISOK) ||
        COMPARE_SEQUENCE(nRcvUrgent,pTCB->n32RcvUrgentSeq) > 0)
    {
      pTCB->n32RcvUrgentSeq = nRcvUrgent;
      pTCB->u16Flags |= TCP_TCB_RCV_URGENT_ISOK;
    }
  }

  if (pPacket->pTCPSeg->u8Code & TCP_CODE_SYN)
  {
    pTCB->n32RcvNextSeq++;
    pTCB->u16Flags |= TCP_TCB_NEED_OUTPUT;
    ++pPacket->pTCPSeg->n32Seq;
  }

  nDataLen = netbufLen(pPacket->segBuf) - TCP_HEADER_LEN(pPacket->pTCPSeg);
  nRcvWin = pTCB->u32RcvBufSize - pTCB->u32RcvBufCount;
  n32WinLast = pTCB->n32RcvNextSeq + nRcvWin - 1;
  n32First = pPacket->pTCPSeg->n32Seq;
  n32Last = n32First + nDataLen - 1;

  if (COMPARE_SEQUENCE(pTCB->n32RcvNextSeq,n32First) > 0)
  {
    nDataLen -= pTCB->n32RcvNextSeq - n32First;
    n32First = pTCB->n32RcvNextSeq;
  }

  if (COMPARE_SEQUENCE(n32Last,n32WinLast) > 0)
  {
    nDataLen -= n32Last - n32WinLast;
    pPacket->pTCPSeg->u8Code &= ~TCP_CODE_FIN;   			// cutting it off
  }

  uRcvBufPos = pTCB->u32RcvBufStart + pTCB->u32RcvBufCount; 	// == rnext, in buf
  uRcvBufPos += n32First - pTCB->n32RcvNextSeq;				// distance in buf
  uRcvBufPos %= pTCB->u32RcvBufSize;							// may wrap
  nSegBufPos = n32First - pPacket->pTCPSeg->n32Seq;

  // copies input data into rcv buffer
  if (nDataLen > 0)
  {
    n32Left = pTCB->u32RcvBufSize - uRcvBufPos;
    if (nDataLen > n32Left)
    {
      // need to copy data in two pieces (buffer wraps)
      n32Copied = netbufRead(&pTCB->pu8rcvBuf[uRcvBufPos],pPacket->segBuf,
                             n32Left,TCP_MIN_HDR_LEN + nSegBufPos);
      if (n32Copied != n32Left)
        return TCP_GENERAL_ERROR;

      n32Copied = netbufRead(&pTCB->pu8rcvBuf[0],pPacket->segBuf,
                             nDataLen - n32Left,TCP_MIN_HDR_LEN + nSegBufPos + n32Left);
      if (n32Copied != (nDataLen - n32Left))
        return TCP_GENERAL_ERROR;
    }
    else
    {
      n32Copied = netbufRead(&pTCB->pu8rcvBuf[uRcvBufPos],pPacket->segBuf,
                             nDataLen,TCP_MIN_HDR_LEN+nSegBufPos);
      if (n32Copied != nDataLen)
        return TCP_GENERAL_ERROR;
    }
  }

  tcpDoData(pTCB,pPacket,n32First,nDataLen);
  if (pTCB->u16Flags & TCP_TCB_NEED_OUTPUT)
    tcpKick(pTCB);
  return TCP_OK;
}

//**************************************************************************************************
//	tcpDoListen
//**************************************************************************************************
static int
tcpDoListen(
  TCBPtr 		pTCB,
  PacketPtr	pPacket)
{
  TCBPtr 			pNewTCB;
  IPAddressUon	uonIpAddress;
  SocketPtr		pSocket;

  if (pPacket->pTCPSeg->u8Code & TCP_CODE_RST)
    return TCP_OK; 	// "parent" TCB still in LISTEN

  if ((pPacket->pTCPSeg->u8Code & TCP_CODE_ACK) || (pPacket->pTCPSeg->u8Code & TCP_CODE_SYN) == 0)
    return tcpReset(pPacket);

  if (tcpTCBAllocate(&pNewTCB) != TCP_OK)
    return TCP_GENERAL_ERROR;

  if (tcpSync(pNewTCB) == TCP_GENERAL_ERROR)
    return TCP_GENERAL_ERROR;

  if (tcpNewSocket(&pSocket) != TCP_OK)
    return TCP_GENERAL_ERROR;
  pNewTCB->pDevice = pSocket;
  pSocket->pTCB = pNewTCB;

  pNewTCB->setState = TCP_STATE_SYNRCVD;
  pNewTCB->setOutState = TCP_OUT_STATE_IDLE;
  pNewTCB->nError = 0;
  pNewTCB->pParentTCB = (struct TCBRec*)pTCB;				// for ACCEPT

  if (tcpGetIPAddr(pPacket->netAttrs,TCP_IP_SRC_ADDR,&uonIpAddress) != TCP_OK)
    return TCP_GENERAL_ERROR;
  if (tcpSetIPAddr(pNewTCB->netParams,TCP_IP_DST_ADDR,&uonIpAddress) != TCP_OK)
    return TCP_GENERAL_ERROR;

  if (tcpGetIPAddr(pPacket->netAttrs,TCP_IP_DST_ADDR,&uonIpAddress) != TCP_OK)
    return TCP_GENERAL_ERROR;
  if (tcpSetIPAddr(pNewTCB->netParams,TCP_IP_SRC_ADDR,&uonIpAddress) != TCP_OK)
    return TCP_GENERAL_ERROR;

  //if (!netattrGet(pPacket->netAttrs,NETATTR_TCP_FROM,&u16Port)) return NULL;
  if (!netattrSet(pNewTCB->netParams,NETATTR_TCP_TO,pPacket->pTCPSeg->u16SrcPort))
    return NULL;

  //if (!netattrGet(pPacket->netAttrs,NETATTR_TCP_TO,&u16Port)) return NULL;
  if (!netattrSet(pNewTCB->netParams,NETATTR_TCP_FROM,pPacket->pTCPSeg->u16DstPort))
    return NULL;

  tcpWindowInit(pTCB,pNewTCB,pPacket);
  pNewTCB->n32FINSeqNum = pNewTCB->n32PUSHSeqNum = 0;
  pNewTCB->u16Flags = TCP_TCB_NEED_OUTPUT;
  gu32TcpPassiveOpens++;
  tcpData(pNewTCB,pPacket);
  semSignal(&pNewTCB->recMutex);

  return TCP_OK;
}

//**************************************************************************************************
//	tcpKillTimers
//	kills all outstanding timers for a TCB
//**************************************************************************************************
static int
tcpKillTimers(
  TCBPtr 	pTCB)
{
  tcp_tmClear(&gOutputPort,pTCB,SEND);
  tcp_tmClear(&gOutputPort,pTCB,RETRANSMIT);
  tcp_tmClear(&gOutputPort,pTCB,PERSIST);
  return TCP_OK;
}

//**************************************************************************************************
//	tcpInput
//	does SYN_SENT state processing
//**************************************************************************************************
static int
tcpSynSent(
  TCBPtr 		pTCB,
  PacketPtr	pPacket)
{
  if ((pPacket->pTCPSeg->u8Code & TCP_CODE_ACK) &&
      ((pPacket->pTCPSeg->n32AckSeq - pTCB->n32InitSndSeq <= 0) ||
       (pPacket->pTCPSeg->n32AckSeq - pTCB->n32SndNext) > 0))
    return tcpReset(pPacket);

  if (pPacket->pTCPSeg->u8Code & TCP_CODE_RST)
  {
    pTCB->setState = TCP_STATE_CLOSED;
    pTCB->nError = TCP_RESET;
    gu32TcpAttemptFails++;
    tcpKillTimers(pTCB);
    semSignal(&pTCB->recOpenCloseSemaphore);
    return TCP_OK;
  }

  if ((pPacket->pTCPSeg->u8Code & TCP_CODE_SYN) == 0)
    return TCP_OK;
  pTCB->u32SndWinSize = pPacket->pTCPSeg->u16Window;
  pTCB->n32SeqOfLastWinUpdt = pPacket->pTCPSeg->n32Seq;
  pTCB->n32RcvNextSeq = pPacket->pTCPSeg->n32Seq;
  pTCB->n32CurAdvWinSeq = pTCB->n32RcvNextSeq + pTCB->u32RcvBufSize;
  tcpAcked(pTCB,pPacket);
  tcpData(pTCB,pPacket);
  pPacket->pTCPSeg->u8Code &= ~TCP_CODE_FIN;

  if (pTCB->u8NextPacketCode & TCP_CODE_SYN)	// our SYN not ACKed
    pTCB->setState = TCP_STATE_SYNRCVD;
  else
  {
    gu32TcpCurrEstab++;
    pTCB->setState = TCP_STATE_ESTABLISHED;
    semSignal(&pTCB->recOpenCloseSemaphore);
  }
  return TCP_OK;
}

//**************************************************************************************************
//	tcpSynReceived
//	do SYN_RCVD state input processing
//**************************************************************************************************
static int
tcpSynReceived(
  TCBPtr 		pTCB,
  PacketPtr	pPacket)
{
  TCBPtr		pParentTCB;

  if (pPacket->pTCPSeg->u8Code & TCP_CODE_RST)
  {
    gu32TcpAttemptFails++;
    if (pTCB->pParentTCB != NULL)
      return tcpTCBDeallocate(pTCB);
    else
      return tcpAbort(pTCB,TCP_REFUSED);
  }

  if (pPacket->pTCPSeg->u8Code & TCP_CODE_SYN)
  {
    gu32TcpAttemptFails++;
    tcpReset(pPacket);
    return tcpAbort(pTCB,TCP_RESET);
  }

  if 	(tcpAcked(pTCB,pPacket) == TCP_GENERAL_ERROR)
    return TCP_OK;

  if (pTCB->pParentTCB != NULL)			// from a passive open
  {
    pParentTCB = (TCBPtr)pTCB->pParentTCB;
    if (semWait(&pParentTCB->recMutex) != TCP_OK)
    {
      gu32TcpAttemptFails++;
      tcpReset(pPacket);
      return tcpTCBDeallocate(pTCB);
    }

    if (pParentTCB->setState != TCP_STATE_LISTEN)
    {
      gu32TcpAttemptFails++;
      tcpReset(pPacket);
      semSignal(&pParentTCB->recMutex);
      return tcpTCBDeallocate(pTCB);
    }

    if (ptCount(&pParentTCB->recListenQueue) >= pParentTCB->nListenQueueSize)
    {
      gu32TcpAttemptFails++;
      tcpReset(pPacket);
      semSignal(&pParentTCB->recMutex);
      return tcpTCBDeallocate(pTCB);
    }

    ptSend(&pParentTCB->recListenQueue,pTCB,0);
    semSignal(&pParentTCB->recMutex);
  }
  else								// from an active open
    semSignal(&pTCB->recOpenCloseSemaphore);

  gu32TcpCurrEstab++;
  pTCB->setState = TCP_STATE_ESTABLISHED;
  tcpData(pTCB,pPacket);
  if (pTCB->u16Flags & TCP_TCB_RCV_DONE)
    pTCB->setState = TCP_STATE_CLOSEWAIT;

  return TCP_OK;
}

//**************************************************************************************************
//	tcpEstablished
//	does ESTABLISHED state input processing
//**************************************************************************************************
static int
tcpEstablished(
  TCBPtr 		pTCB,
  PacketPtr	pPacket)
{
  if (pPacket->pTCPSeg->u8Code & TCP_CODE_RST)
  {
    gu32TcpEstabResets++;
    gu32TcpCurrEstab--;
    return tcpAbort(pTCB,TCP_RESET);
  }

  if (pPacket->pTCPSeg->u8Code & TCP_CODE_SYN)
  {
    gu32TcpEstabResets++;
    gu32TcpCurrEstab--;
    tcpReset(pPacket);
    return tcpAbort(pTCB,TCP_RESET);
  }

  if (tcpAcked(pTCB,pPacket) == TCP_GENERAL_ERROR)
    return TCP_OK;
  tcpData(pTCB,pPacket);
  tcpSndWindow(pTCB,pPacket);
  if (pTCB->u16Flags & TCP_TCB_RCV_DONE)
    pTCB->setState = TCP_STATE_CLOSEWAIT;
  return TCP_OK;
}

//**************************************************************************************************
//	(re)schedules a DELETE event for 2MSL from now
//**************************************************************************************************
static int
tcpWait(
  TCBPtr pTCB)
{
  tcpKillTimers(pTCB);
  tcp_tmSet(&gOutputPort,TCP_QUEUE_LEN,pTCB,DELETE,TCP_TWOMSL);
  return TCP_OK;
}

//**************************************************************************************************
//	tcpFin1
//	does FIN_WAIT_1 state input processing
//**************************************************************************************************
static int
tcpFin1(
  TCBPtr 		pTCB,
  PacketPtr	pPacket)
{
  if (pPacket->pTCPSeg->u8Code & TCP_CODE_RST)
    return tcpAbort(pTCB,TCP_RESET);

  if (pPacket->pTCPSeg->u8Code & TCP_CODE_SYN)
  {
    tcpReset(pPacket);
    return tcpAbort(pTCB,TCP_RESET);
  }

  if (tcpAcked(pTCB,pPacket) == TCP_GENERAL_ERROR)
    return TCP_OK;
  tcpData(pTCB,pPacket);
  tcpSndWindow(pTCB,pPacket);

  if (pTCB->u16Flags & TCP_TCB_RCV_DONE)
  {
    if (pTCB->u8NextPacketCode & TCP_CODE_FIN)		// FIN not ACKed
      pTCB->setState = TCP_STATE_CLOSING;
    else
    {
      pTCB->setState = TCP_STATE_TIMEWAIT;
      semSignal(&pTCB->recOpenCloseSemaphore);		// wake closer
      tcpWait(pTCB);
    }
  }
  else if ((pTCB->u8NextPacketCode & TCP_CODE_FIN) == 0)
  {
    semSignal(&pTCB->recOpenCloseSemaphore);
    pTCB->setState = TCP_STATE_FINWAIT2;
  }

  return TCP_OK;
}

//**************************************************************************************************
//	tcpFin2
//	does FIN_WAIT_2 state input processing
//**************************************************************************************************
static int
tcpFin2(
  TCBPtr 		pTCB,
  PacketPtr	pPacket)
{
  if (pPacket->pTCPSeg->u8Code & TCP_CODE_RST)
    return tcpAbort(pTCB,TCP_RESET);

  if (pPacket->pTCPSeg->u8Code & TCP_CODE_SYN)
  {
    tcpReset(pPacket);
    return tcpAbort(pTCB,TCP_RESET);
  }

  if (tcpAcked(pTCB,pPacket) == TCP_GENERAL_ERROR)
    return TCP_OK;
  tcpData(pTCB,pPacket);					// for data + FIN ACKing

  if (pTCB->u16Flags & TCP_TCB_RCV_DONE)
  {
    pTCB->setState = TCP_STATE_TIMEWAIT;
    tcpWait(pTCB);
  }

  return TCP_OK;
}

//**************************************************************************************************
//	tcpCloseWait
//	does CLOSE_WAIT state input processing
//**************************************************************************************************
static int
tcpCloseWait(
  TCBPtr 		pTCB,
  PacketPtr	pPacket)
{
  if (pPacket->pTCPSeg->u8Code & TCP_CODE_RST)
  {
    gu32TcpEstabResets++;
    gu32TcpCurrEstab--;
    return tcpAbort(pTCB,TCP_RESET);
  }

  if (pPacket->pTCPSeg->u8Code & TCP_CODE_SYN)
  {
    gu32TcpEstabResets++;
    gu32TcpCurrEstab--;
    tcpReset(pPacket);
    return tcpAbort(pTCB,TCP_RESET);
  }

  tcpAcked(pTCB,pPacket);
  tcpSndWindow(pTCB,pPacket);

  return TCP_OK;
}

//**************************************************************************************************
//	tcpLastAck
//	does LAST_ACK state input processing
//**************************************************************************************************
static int
tcpLastAck(
  TCBPtr 		pTCB,
  PacketPtr	pPacket)
{
  if (pPacket->pTCPSeg->u8Code & TCP_CODE_RST)
    return tcpAbort(pTCB,TCP_RESET);

  if (pPacket->pTCPSeg->u8Code & TCP_CODE_SYN)
  {
    tcpReset(pPacket);
    return tcpAbort(pTCB,TCP_RESET);
  }

  tcpAcked(pTCB,pPacket);
  if ((pTCB->u8NextPacketCode & TCP_CODE_FIN) == 0)
    semSignal(&pTCB->recOpenCloseSemaphore);

  return TCP_OK;
}

//**************************************************************************************************
//	tcpClosing
//	does CLOSING state input processing
//**************************************************************************************************
static int
tcpClosing(
  TCBPtr 		pTCB,
  PacketPtr	pPacket)
{
  if (pPacket->pTCPSeg->u8Code & TCP_CODE_RST)
    return tcpTCBDeallocate(pTCB);

  if (pPacket->pTCPSeg->u8Code & TCP_CODE_SYN)
  {
    tcpReset(pPacket);
    return tcpTCBDeallocate(pTCB);
  }

  tcpAcked(pTCB,pPacket);

  if ((pTCB->u8NextPacketCode & TCP_CODE_FIN) == 0)
  {
    pTCB->setState = TCP_STATE_TIMEWAIT;
    semSignal(&pTCB->recOpenCloseSemaphore);
    tcpWait(pTCB);
  }

  return TCP_OK;
}

//**************************************************************************************************
//	tcpTimeWait
//	does TIME_WAIT state input processing
//**************************************************************************************************
static int
tcpTimeWait(
  TCBPtr 		pTCB,
  PacketPtr	pPacket)
{
  if (pPacket->pTCPSeg->u8Code & TCP_CODE_RST)
    return tcpTCBDeallocate(pTCB);

  if (pPacket->pTCPSeg->u8Code & TCP_CODE_SYN)
  {
    tcpReset(pPacket);
    return tcpTCBDeallocate(pTCB);
  }

  tcpAcked(pTCB,pPacket);
  tcpData(pTCB,pPacket);			// just ACK any packets
  tcpWait(pTCB);

  return TCP_OK;
}

//**************************************************************************************************
//	tcpInput
//**************************************************************************************************
static void
tcpInputStateDispatcher(
  TCBPtr 		pTCB,
  PacketPtr	pPacket)
{
  switch(pTCB->setState)
  {
  case TCP_STATE_CLOSED:
    tcpClosed(pTCB,pPacket);
    break;
  case TCP_STATE_LISTEN:
    tcpDoListen(pTCB,pPacket);
    break;
  case TCP_STATE_SYNSENT:
    tcpSynSent(pTCB,pPacket);
    break;
  case TCP_STATE_SYNRCVD:
    tcpSynReceived(pTCB,pPacket);
    break;
  case TCP_STATE_ESTABLISHED:
    tcpEstablished(pTCB,pPacket);
    break;
  case TCP_STATE_FINWAIT1:
    tcpFin1(pTCB,pPacket);
    break;
  case TCP_STATE_FINWAIT2:
    tcpFin2(pTCB,pPacket);
    break;
  case TCP_STATE_CLOSEWAIT:
    tcpCloseWait(pTCB,pPacket);
    break;
  case TCP_STATE_LASTACK:
    tcpLastAck(pTCB,pPacket);
    break;
  case TCP_STATE_CLOSING:
    tcpClosing(pTCB,pPacket);
    break;
  case TCP_STATE_TIMEWAIT:
    tcpTimeWait(pTCB,pPacket);
    break;
  default:
    break;
  }
}

//**************************************************************************************************
//	tcpInput
//**************************************************************************************************
void
tcpInput(
  NetBuf 		buf,
  NetAttrs 	attrs)
{
  TCBPtr		pTCB;
  PacketRec	recPacket;
  int			err;

  recPacket.pTCPSeg = (TCPSegPtr)NETBUF_DATA(buf);
  recPacket.segBuf = buf;
  recPacket.netAttrs = attrs;
  err = TCP_NO_ERR;

  if (netbufLen(recPacket.segBuf) < sizeof(TCPSegRec))
    err = TCP_HEADER_TOO_SHORT_ERR;
  else if (netbufLen(recPacket.segBuf) < ntohs(TCP_HEADER_LEN(recPacket.pTCPSeg)))
    err = TCP_HEADER_TOO_SHORT_ERR;
  else
  {
    tcpPacketDump(recPacket.pTCPSeg,"RN",netbufLen(buf));
    if (recPacket.pTCPSeg->u16CheckSum != 0)
    {
      if (tcpCheckSum(buf,attrs,netbufLen(buf)) != 0)
        err = TCP_CHECKSUM_ERR;
      else
      {
        // converts to local host byte order
        tcpHeaderNet2Host(recPacket.pTCPSeg);

        // searches the TCB block which maches current tcp port
        pTCB = tcpDemux(&recPacket);
        if (pTCB == NULL)
        {
          // unexpected segement has arrived. generates and sends a reset.
          tcpReset(&recPacket);
          err = TCP_NO_LISTENER_ERR;
        }
        else
        {
          if (tcpIsOK(pTCB,&recPacket))
          {
            tcpOptions(pTCB,&recPacket);
            tcpInputStateDispatcher(pTCB,&recPacket);
          }
          else
          {
            tcpAckIt(pTCB,&recPacket);
          }

          semSignal(&pTCB->recMutex);
        }
      }
    }
  }

  netbufFree(buf);
  NETATTR_FREE(attrs);

  // error occured
  if (err != TCP_NO_ERR)
  {
    netdbgPrintf(NETDEBUG_TCP,"tcp: Discarding upcoming packet. (%d)\n",err);
    ++gu32TcpInErrors;
  }
}

//**************************************************************************************************
//	tcpTCBInit
//**************************************************************************************************
static long
tcpInitialSequence(void)
{
  static	long		seq = 0;

  if (seq == 0)
    seq = tmUserTimerGetTicks();

  seq += TCPINCR;
  return seq;
}

//**************************************************************************************************
//	tcpSync
//	initializes TCB for a new connection request
//**************************************************************************************************
int
tcpSync(
  TCBPtr	pTCB)	// Uninitialized TCB data structure
{
  long	n32Seq;

  pTCB->setState = TCP_STATE_CLOSED;

  pTCB->setType = TCP_TYPE_CONNECTION;

  n32Seq = tcpInitialSequence();
  pTCB->n32InitSndSeq = n32Seq;
  pTCB->n32SndUnacked = n32Seq;
  pTCB->n32SndNext = n32Seq;
  pTCB->n32AckSeqOfLastWinUpdt = n32Seq;

  pTCB->pu8sndBuf=tcpAlloc(TCP_SND_BUF_SIZE);
  if (pTCB->pu8sndBuf == NULL)
    return TCP_GENERAL_ERROR;

  pTCB->u32SndBufSize = TCP_SND_BUF_SIZE;
  pTCB->u32SndBufStart = 0;
  pTCB->u32SndBufCount = 0;
  semInit(&pTCB->recSndSemaphore,1);

  pTCB->pu8rcvBuf=tcpAlloc(TCP_RCV_BUF_SIZE);
  if (pTCB->pu8rcvBuf == NULL)
    return TCP_GENERAL_ERROR;

  pTCB->u32RcvBufSize = TCP_RCV_BUF_SIZE;
  pTCB->u32RcvBufStart = 0;
  pTCB->u32RcvBufCount = 0;
  pTCB->nRcvSegFragQueue = EMPTY;
  semInit(&pTCB->recRcvSemaphore,0);
  semInit(&pTCB->recOpenCloseSemaphore,0);

  // timer stuff
  pTCB->nSmoothedRoundTripTime = 0; 		// in sec/100
  pTCB->nRoundTripDeviationEst = 0; 		// in sec/100
  pTCB->nRetransmitTimeout = 50; 			// in sec/100

  pTCB->nNumOfRetransmits = 0;
  pTCB->u8NextPacketCode = TCP_CODE_SYN;
  pTCB->u16Flags = 0;

  return TCP_OK;
}

//**************************************************************************************************
//	tcpTCBAllocate
//**************************************************************************************************
int
tcpTCBAllocate(
  TCBPtr *ppTCB)				/* allocated tcb data structure */
{
  *ppTCB = (TCBPtr)tcpAlloc(sizeof(TCBRec));
  if (*ppTCB == NULL)
    return TCP_GENERAL_ERROR;

  semWait(&gTCBLinkedListMutex);
  (**ppTCB).pNext = (struct TCBRec*)gTCPTCBList;
  gTCPTCBList = *ppTCB;
  semSignal(&gTCBLinkedListMutex);

  // allocates tcb internal netattrs
  if (netattrNew(&(**ppTCB).netParams) == 0)
  {
    tcpFree((unsigned char *)*ppTCB);
    return TCP_GENERAL_ERROR;
  }

  // allocates tcb mutex semaphore
  semInit(&(**ppTCB).recMutex,0);

  // basic initialisation
  (**ppTCB).setState = TCP_STATE_CLOSED;
  (**ppTCB).pDevice = NULL;
  (**ppTCB).pParentTCB = NULL;

  return TCP_OK;
}

//**************************************************************************************************
//	tcpTCBDeallocate
//	deallocate a TCB and free its resources ASSUMES ptcb->tcb_mutex HELD
//**************************************************************************************************
int
tcpTCBDeallocate(TCBPtr pTCB)
{
  TCBPtr pCurrentTCB,pOneBeforeCurrentTCB;

  switch(pTCB->setType)
  {
  case TCP_TYPE_CONNECTION:
    tcpKillTimers(pTCB);
    tcpFree(pTCB->pu8sndBuf);
    tcpFree(pTCB->pu8rcvBuf);
    if (pTCB->nRcvSegFragQueue >= 0)
      pqFreeq(pTCB->nRcvSegFragQueue);
    if (pTCB->netParams != NULL)
      NETATTR_FREE(pTCB->netParams);
    break;
  case TCP_TYPE_SERVER:
    break;
  default:
    semSignal(&pTCB->recMutex);
    return TCP_GENERAL_ERROR;
  }

  // deallocates memory
  semWait(&gTCBLinkedListMutex);
  pCurrentTCB = gTCPTCBList;
  pOneBeforeCurrentTCB = gTCPTCBList;
  while ((pCurrentTCB != NULL) && (pCurrentTCB != pTCB))
  {
    pOneBeforeCurrentTCB = pCurrentTCB;
    pCurrentTCB = (TCBPtr)pCurrentTCB->pNext;
  }
  if (pOneBeforeCurrentTCB == gTCPTCBList)
    gTCPTCBList = (TCBPtr)pTCB->pNext;
  else
    pOneBeforeCurrentTCB->pNext = pTCB->pNext;
  semSignal(&gTCBLinkedListMutex);
  tcpFree((unsigned char *)pTCB);

  return TCP_OK;
}
