/* BlueDataDistributor - data distribution module from ethernet to 32 serial ports
 * version 0.1.1 date 2006-10-07
 * Copyright (C) 2006 Stefan Schuermans <stefan@blinkenarea.org>
 * a BlinkenArea project - http://www.blinkenarea.org/
 */

#include <stdio.h>

#include "config.h"
#include "checksum.h"
#include "ethernet.h"
#include "ip.h"
#include "macros.h"
#include "nethelp.h"
#include "random.h"
#include "tcp.h"

#define TCP_URG 0x20
#define TCP_ACK 0x10
#define TCP_PSH 0x08
#define TCP_RST 0x04
#define TCP_SYN 0x02
#define TCP_FIN 0x01
#define TCP_FLAGS 0x3F

#define TcpResendTicks 5 // time after which to resend a packet not ACKed (in 200ms steps, max. 255)
#define TcpTimeWaitTicks 20 // time to wait in TIME_WAIT state (in 200ms steps, max. 255)
#define TcpTimeoutTicks 50 // maximum idle time of connection before it is reset (in 200ms steps, max. 255)
#define TcpMaxLifeTimeTicks 150 // maximum lifetime of connection before it is reset (in 200ms steps, max. 255)

// TCP connections
struct TcpConnection TcpConns[8];

// initialize
void TcpInit( void ) // (extern)
{
  unsigned char i;

  // set all connections to closed
  for( i = 0; i < count( TcpConns ); i++ )
    TcpConns[i].State = TCP_CLOSED;
}

// send a TCP packet
// pData must point to a struct TcpPacket with TcpHdr.SrcPort, TcpHdr.DestPort,
// TcpHdr.SeqNo, TcpHdr.AckNo, TcpHdr.WndSz and IpHdr.Dest already initialized
static void TcpSendPacket( unsigned char * pData, unsigned short Length, unsigned char optLen, unsigned char flags )
{
  struct TcpPacket * pTcpPack;
  unsigned int chk;

  // packet too short
  if( Length < sizeof( struct TcpPacket ) + optLen )
    return;

  // convert pointer to UDP packet
  // (this saves us from always casting pData)
  pTcpPack = (struct TcpPacket *)pData;

  // fill in header values
  pTcpPack->TcpHdr.Ofs_Flags = htons( (unsigned short)((optLen + 23) & 0x3C) << 10 | (unsigned short)(flags & TCP_FLAGS) );
  pTcpPack->TcpHdr.Chk = 0x0000;
  pTcpPack->TcpHdr.UrgentPtr = 0x0000;
  ip_cpy( pTcpPack->IpHdr.Src, ConfigIp ); // put IP already here into IP header
                                           // because it is needed for calculation of UDP checksum

  // generate checksum
  chk = Checksum( (unsigned char *)&pTcpPack->IpHdr.Src,
                  Length - sizeof( struct EthernetHeader ) - sizeof( struct IpHeader ) + 8,
                  0x0006,
                  Length - sizeof( struct EthernetHeader ) - sizeof( struct IpHeader ) );
  pTcpPack->TcpHdr.Chk = htons( chk );

  // send TCP packet
  pTcpPack->IpHdr.Proto = 0x06; // TCP
  IpSend( pData, Length );
}

// send an empty segment
static void TcpEmptySegment( struct TcpConnection * pConn, unsigned long seq, unsigned long ack, unsigned char flags )
{
  struct TcpPacket EsPack;

  // send empty TCP segment
  EsPack.TcpHdr.SrcPort = htons( pConn->LocalPort );
  EsPack.TcpHdr.DestPort = htons( pConn->RemotePort );
  EsPack.TcpHdr.SeqNo = htonl( seq );
  EsPack.TcpHdr.AckNo = htonl( ack );
  EsPack.TcpHdr.WndSz = htons( pConn->RcvWnd );
  ip_cpy( EsPack.IpHdr.Dest, pConn->RemoteIp );
  TcpSendPacket( (unsigned char *)&EsPack, sizeof( EsPack ), 0, flags );
  return;
}

// send a SYN segment (empty with MSS option set)
static void TcpSynSegment( struct TcpConnection * pConn, unsigned long seq, unsigned long ack, unsigned char flags )
{
  struct
  {
    struct TcpPacket Tcp;
    struct
    {
      unsigned char Kind, Len;
      unsigned short Mss;
    } MssOpt;
  } Pack;

  // send empty TCP segment with MSS
  Pack.Tcp.TcpHdr.SrcPort = htons( pConn->LocalPort );
  Pack.Tcp.TcpHdr.DestPort = htons( pConn->RemotePort );
  Pack.Tcp.TcpHdr.SeqNo = htonl( seq );
  Pack.Tcp.TcpHdr.AckNo = htonl( ack );
  Pack.Tcp.TcpHdr.WndSz = htons( pConn->RcvWnd );
  ip_cpy( Pack.Tcp.IpHdr.Dest, pConn->RemoteIp );
  Pack.MssOpt.Kind = 2;
  Pack.MssOpt.Len = 4;
  Pack.MssOpt.Mss = htons( 256 );
  TcpSendPacket( (unsigned char *)&Pack, sizeof( Pack ), sizeof( Pack.MssOpt ), flags );
  return;
}

// send a data segment
static void TcpSendDataSegment( unsigned char connNo, struct TcpConnection * pConn, char needSendAck )
{
  unsigned long sendDataLenL;
  unsigned short sendDataLen, len;
  struct
  {
    struct TcpPacket Tcp;
    unsigned char Data[256]; // do not use larger segments to save stack memory
  } Packet;

  // if outbound connection is not closed yet
  if( pConn->State == TCP_ESTAB || pConn->State == TCP_CLOSE_WAIT )
  {
    // get maximum number of bytes that might be sent in this segment
    sendDataLenL = (unsigned long)pConn->SndWnd - (pConn->SndNxt - pConn->SndUna);
    if( (signed long)sendDataLenL < 0 ) // just to be on the safe side - this should never happen
      sendDataLen = 0;
    sendDataLen = (unsigned short)min( sendDataLenL, (unsigned long)pConn->Mss );
    sendDataLen = min( sendDataLen, sizeof( Packet.Data ) );
    // get data to send from user
    if( sendDataLen > 0 )
    {
      len = pConn->Notify->Send( connNo, pConn->SndNxt - (pConn->Iss + 1), Packet.Data, sendDataLen );
      if( len > sendDataLen ) // returned 0xFFFF (or any number too large) to close connection
      {
        TcpEmptySegment( pConn, pConn->SndNxt, pConn->RcvNxt, TCP_FIN | TCP_ACK ); // send TCP FIN
        pConn->SndNxt++;
        pConn->State = TCP_FIN_WAIT_1; // go to state FIN-WAIT-1
        pConn->Notify->Close( connNo ); // signal "connection closed" to user
        return;
      }
      sendDataLen = len;
    }
  }
  // if outbound connection is already closed
  else
    // do not send data
    sendDataLen = 0;

  // send a segment if data is available or an ACK needs to be sent
  if( sendDataLen > 0 || needSendAck )
  {
    // send TCP segment
    Packet.Tcp.TcpHdr.SrcPort = htons( pConn->LocalPort );
    Packet.Tcp.TcpHdr.DestPort = htons( pConn->RemotePort );
    Packet.Tcp.TcpHdr.SeqNo = htonl( pConn->SndNxt );
    Packet.Tcp.TcpHdr.AckNo = htonl( pConn->RcvNxt );
    Packet.Tcp.TcpHdr.WndSz = htons( pConn->RcvWnd );
    ip_cpy( Packet.Tcp.IpHdr.Dest, pConn->RemoteIp );
    TcpSendPacket( (unsigned char *)&Packet, sizeof( struct TcpPacket ) + sendDataLen, 0,
                   sendDataLen > 0 ? TCP_ACK | TCP_PSH : TCP_ACK );
    pConn->SndNxt += sendDataLen;
  }
}

// tick procedure - call every 200ms
void TcpTick200( void ) // (extern)
{
  unsigned char i, MaxTicks;

  // for all active connections
  for( i = 0; i < count( TcpConns ); i++ )
  {

    if( TcpConns[i].State != TCP_CLOSED )
    {
      // increase normal timer
      TcpConns[i].Ticks++;

      // get maximum value for normal timer
      MaxTicks = TcpConns[i].State == TCP_TIME_WAIT ? TcpTimeWaitTicks : TcpResendTicks;
      // normal timer elapsed
      if( TcpConns[i].Ticks >= MaxTicks )
      {
        // reset normal timer
        TcpConns[i].Ticks = 0;

        // different behaviour in different states
        switch( TcpConns[i].State )
        {

          case TCP_SYN_RCVD:
            // resend SYN,ACK segment
            TcpSynSegment( &TcpConns[i], TcpConns[i].SndUna, TcpConns[i].RcvNxt, TCP_SYN | TCP_ACK );
            TcpConns[i].SndNxt = TcpConns[i].SndUna + 1;
            break;

          case TCP_SYN_SENT:
            // resend SYN segment
            TcpSynSegment( &TcpConns[i], TcpConns[i].SndUna, 0, TCP_SYN );
            TcpConns[i].SndNxt = TcpConns[i].SndUna + 1;
            break;

          case TCP_ESTAB:
          case TCP_CLOSE_WAIT:
            // if something is not yet ACKed
            if( (long)(TcpConns[i].SndUna - TcpConns[i].SndNxt) < 0 )
              // resend data segment
              TcpConns[i].SndNxt = TcpConns[i].SndUna; // some kind of "go back N"
              // BUG: this is not really "go back N"
              //      according to RFC793, every segment sent and not acknowledged has to be stored
              //      in the resend queue until is is acknowledged
              //      but this is not possible on a microcontroller with 4kB of RAM
              TcpSendDataSegment( i, &TcpConns[i], 0 );
            break;

          case TCP_FIN_WAIT_1:
          case TCP_CLOSING:
          case TCP_LAST_ACK:
            // resend FIN segment
            TcpEmptySegment( &TcpConns[i], TcpConns[i].SndUna, TcpConns[i].RcvNxt, TCP_FIN | TCP_ACK );
            TcpConns[i].SndNxt = TcpConns[i].SndUna + 1;
            break;

          case TCP_TIME_WAIT:
            // close connection, free TCB
            TcpConns[i].State = TCP_CLOSED;
            break;

        } // switch( TcpConns[i].State )

      } // if( TcpConns[i].Ticks >= ...

    } // if( TcpConns[i].State != ...

    if( TcpConns[i].State != TCP_CLOSED )
    {
      // increase timeout timer
      TcpConns[i].Timeout++;

      // timeout timer elapsed
      if( TcpConns[i].Timeout >= TcpTimeoutTicks )
      {
        // send a RST segment
        TcpEmptySegment( &TcpConns[i], TcpConns[i].SndUna, TcpConns[i].RcvNxt, TCP_RST );
        // depending on state ...
        switch( TcpConns[i].State )
        {
          case TCP_SYN_RCVD:
          case TCP_ESTAB:
          case TCP_CLOSE_WAIT:
            TcpConns[i].State = TCP_CLOSED; // close connection
            TcpConns[i].Notify->Close( i ); // tell user that connection was closed
            break;
          default:
            TcpConns[i].State = TCP_CLOSED; // close connection
        }

      } // if( TcpConns[i].Timeout >= ...

    } // if( TcpConns[i].State != ...

    if( TcpConns[i].State != TCP_CLOSED )
    {
      // increase lifetime timer
      TcpConns[i].LifeTime++;

      // lifetime timer elapsed
      //  - connections may not last forever - even not with traffic on them
      if( TcpConns[i].LifeTime >= TcpMaxLifeTimeTicks )
      {
        // send a RST segment
        TcpEmptySegment( &TcpConns[i], TcpConns[i].SndUna, TcpConns[i].RcvNxt, TCP_RST );
        // depending on state ...
        switch( TcpConns[i].State )
        {
          case TCP_SYN_RCVD:
          case TCP_ESTAB:
          case TCP_CLOSE_WAIT:
            TcpConns[i].State = TCP_CLOSED; // close connection
            TcpConns[i].Notify->Close( i ); // tell user that connection was closed
            break;
          default:
            TcpConns[i].State = TCP_CLOSED; // close connection
        }
      } // if( TcpConns[i].LifeTime >= ...

    } // if( TcpConns[i].State != ...

  } // for( i ...
}

// process a received TCP packet
void TcpRecv( unsigned char * pData, unsigned short Length ) // (extern)
{
  struct TcpPacket * pTcpPack;
  unsigned long seq, ack, seqEnd;
  unsigned short localPort, remotePort, wnd, mss, ofs, len, tmp, rcvWnd;
  unsigned char i, flags, * optPtr, optLen, connNo;
  struct TcpConnection * pConn;
  char accept, sendAck;
  struct TcpNotify * pNotify;

  // packet too short
  if( Length < sizeof( struct TcpPacket ) )
    return;

  // convert pointer to TCP packet
  // (this saves us from always casting pData)
  pTcpPack = (struct TcpPacket *)pData;

  // test checksum
  if( Checksum( (unsigned char*)&pTcpPack->IpHdr.Src,
                Length - sizeof( struct EthernetHeader ) - sizeof( struct IpHeader ) + 8,
                0x0006,
                Length - sizeof( struct EthernetHeader ) - sizeof( struct IpHeader ) ) != 0 )
    return;

  // get local and remote port
  localPort = ntohs( pTcpPack->TcpHdr.DestPort );
  remotePort = ntohs( pTcpPack->TcpHdr.SrcPort );

  // ignore packets sent from or to port 0
  //  - this might be some attack
  if( localPort == 0 || remotePort == 0 )
    return;

  // get sequence number, acknowledge number and window size
  seq = ntohl( pTcpPack->TcpHdr.SeqNo );
  ack = ntohl( pTcpPack->TcpHdr.AckNo );
  wnd = ntohs( pTcpPack->TcpHdr.WndSz );
  // maximum segment size: liberal default according to RFC879
  mss = 536;

  // get flags
  flags = ntohs( pTcpPack->TcpHdr.Ofs_Flags ) & 0x003F;

  // get data offset and segment length in bytes
  ofs = (ntohs( pTcpPack->TcpHdr.Ofs_Flags ) & 0xF000) >> 10;
  if( ofs < 20 || ofs > Length - sizeof( struct EthernetHeader ) - sizeof( struct IpHeader ) ) // invalid offset
    return; // remote side is unable to build valid TCP packets - ignore

  // get segment length (length of data in segment)
  len = Length - sizeof( struct EthernetHeader ) - sizeof( struct IpHeader ) - ofs;

  // process options
  optLen = (unsigned char)(ofs - 20);
  optPtr = (unsigned char *)&pTcpPack->TcpHdr + sizeof( struct TcpHeader );
  while( optLen > 0 )
  {
    switch( *optPtr )
    {
      // end of options
      case 0:
        optLen = 0;
        break;
      // no operation
      case 1:
        optLen--;
        optPtr++;
        break;
      // maximum segment size
      case 2:
        if( optLen < 4 || optPtr[1] != 4 )
        {
          optPtr = NULL; // error
          optLen = 0;
          break;
        }
        mss = ntohs( *(unsigned short *)(optPtr+2) );
        optLen -= 4;
        optPtr += 4;
        break;
      // unknown option
      default:
        if( optLen < 2 || optPtr[1] > optLen )
        {
          optPtr = NULL; // error
          optLen = 0;
        }
        optLen -= optPtr[1]; // ignore this option
        optPtr += optPtr[1];
    } // switch( *optPtr )
  } // while( optLen > 0 )
  if( optPtr == NULL ) // some error during option parsing
    return; // remote side is unable to build valid TCP packets - ignore

  // get sequence number at end of this segment
  seqEnd = seq + len;
  if( flags & TCP_SYN ) // TCP SYN counts as 1 in sequence number space
    seqEnd++;
  if( flags & TCP_FIN ) // TCP FIN counts as 1 in sequence number space
    seqEnd++;

  // search connection
  for( i = 0; i < count( TcpConns ); i++ )
    if( TcpConns[i].State != TCP_CLOSED &&
        ip_eq( TcpConns[i].RemoteIp, pTcpPack->IpHdr.Src ) &&
        TcpConns[i].LocalPort == localPort &&
        TcpConns[i].RemotePort == remotePort )
      break;

  // connection not found, only SYN flag set, no data
  if( i >= count( TcpConns ) && flags == TCP_SYN && len == 0 )
  {
    // accept connections on some ports (note: localPort cannot be 0 because of check above)
    rcvWnd = 0;
    pNotify = NULL;
    // services may be added here
    // if( localPort == _PORT_ )
    // {
    //   rcvWnd = 32; // connection shall be accepted
    //   pNotify = &_SERVICE_Notify;
    // }
    // connection shall be accepted
    if( pNotify != NULL )
    {
      // search empty connection slot
      for( i = 0; i < count( TcpConns ); i++ )
        if( TcpConns[i].State == TCP_CLOSED )
          break;
      // free connection slot found
      if( i < count( TcpConns ) )
      {
        // create new connection (passive) in this slot
        ip_cpy( TcpConns[i].RemoteIp, pTcpPack->IpHdr.Src );
        TcpConns[i].LocalPort = localPort;
        TcpConns[i].RemotePort = remotePort;
        TcpConns[i].State = TCP_LISTEN;
        TcpConns[i].Ticks = 0;
        TcpConns[i].Timeout = 0;
        TcpConns[i].LifeTime = 0;
        TcpConns[i].RcvWnd = rcvWnd;
        TcpConns[i].Notify = pNotify;
      }
    }
  }

  // connection still not found
  if( i >= count( TcpConns ) )
  {
    struct TcpPacket EsPack;
    unsigned char EsFlags;

    // do nothing if RST flag is set
    if( flags & TCP_RST )
      return;

    // send TCP RST
    EsPack.TcpHdr.SrcPort = htons( localPort );
    EsPack.TcpHdr.DestPort = htons( remotePort );
    EsPack.TcpHdr.WndSz = htons( 0 );
    ip_cpy( EsPack.IpHdr.Dest, pTcpPack->IpHdr.Src );
    if( flags & TCP_ACK )
    {
      EsPack.TcpHdr.SeqNo = htonl( ack );
      EsPack.TcpHdr.AckNo = htonl( 0 );
      EsFlags = TCP_RST;
    }
    else
    {
      EsPack.TcpHdr.SeqNo = htonl( 0 );
      EsPack.TcpHdr.AckNo = htonl( seqEnd );
      EsFlags = TCP_RST | TCP_ACK;
    }
    TcpSendPacket( (unsigned char *)&EsPack, sizeof( EsPack ), 0, EsFlags );
    return;
  }

  // a connection was found - save number and pointer to it
  connNo = i;
  pConn = &TcpConns[i];

  // reset connection on reception of urgent data (URG flag set)
  // BUG: urgent data must be supported according to RFC793
  //      but urgent data is not used in protocols we use, so leave this out here to save time and memory
  if( flags & TCP_URG )
  {
    // send TCP RST
    if( flags & TCP_ACK )
      TcpEmptySegment( pConn, ack, 0, TCP_RST );
    else
      TcpEmptySegment( pConn, 0, seqEnd, TCP_RST | TCP_ACK );
    return;
  }

  // different behaviour in different states according to RFC793
  switch( pConn->State )
  {

    case TCP_LISTEN:
      if( flags & TCP_RST ) // An incoming RST should be ignored.
        return;
      if( flags & TCP_ACK ) // Any acknowledgment is bad if it arrives on a connection still in the LISTEN state.
      {
        TcpEmptySegment( pConn, ack, 0, TCP_RST ); // An acceptable reset segment should be formed for any arriving ACK-bearing segment.
        pConn->Ticks = 0; // restart timer
        return;
      }
      if( flags & TCP_SYN ) // third check for a SYN
      {
        pConn->RcvNxt = seq + 1; // Set RCV.NXT to SEG.SEQ+1,
        pConn->Irs = seq; // IRS is set to SEG.SEQ
        RandomGetData( (unsigned char *)&pConn->Iss, sizeof( pConn->Iss ) ); // ISS should be selected (randomly!!!)
        TcpSynSegment( pConn, pConn->Iss, pConn->RcvNxt, TCP_SYN | TCP_ACK ); // and a SYN segment sent
        pConn->SndNxt = pConn->Iss + 1; // SND.NXT is set to ISS+1
        pConn->SndUna = pConn->Iss; // and SND.UNA to ISS
        pConn->State = TCP_SYN_RCVD; // The connection state should be changed to SYN-RECEIVED.
        pConn->SndWnd = wnd; // initialize send window from packet
        pConn->SndWl1 = seq;
        pConn->SndWl2 = ack;
        pConn->Mss = mss; // save maximum segment size
        pConn->Ticks = 0; // restart timer
        return;
      }
      // fourth other text or control
      // So you are unlikely to get here, but if you do, drop the segment, and return.
      return;

    case TCP_SYN_SENT:
      accept = 0;
      if( flags & TCP_ACK ) // If the ACK bit is set
      {
        if( (long)(ack - pConn->Iss) <= 0 || (long)(ack - pConn->SndNxt) > 0 ) // If SEG.ACK =< ISS, or SEG.ACK > SND.NXT,
          {
            if( ! (flags & TCP_RST) ) // (unless the RST bit is set)
              TcpEmptySegment( pConn, ack, 0, TCP_RST ); // send a reset
            return; // and discard the segment. Return.
          }
        accept = (long)(pConn->SndUna - ack) <= 0 && (long)(ack - pConn->SndNxt) <= 0; // If SND.UNA =< SEG.ACK =< SND.NXT then the ACK is acceptable.
      }
      if( flags & TCP_RST ) // If the RST bit is set
      {
        if( accept ) // If the ACK was acceptable
          pConn->State = TCP_CLOSED; // enter CLOSED state, delete TCB,
        return; // drop the segment and return.
      }
      if( (accept || ! (flags & TCP_ACK)) && // This step should be reached only if the ACK is ok, or there is no ACK,
          flags & TCP_SYN ) // If the SYN bit is on
      {
        pConn->RcvNxt = seq + 1; // RCV.NXT is set to SEG.SEQ+1,
        pConn->Irs = seq; // IRS is set to SEG.SEQ.
        pConn->SndWl2 = pConn->RcvNxt; // last acknowledge number when updating the window size is first acknowledge number at all
        if( accept ) // (if there is an ACK)
          pConn->SndUna = ack; // SND.UNA should be advanced to equal SEG.ACK
        pConn->SndWnd = wnd; // initialize send window from packet
        pConn->SndWl1 = seq;
        pConn->SndWl2 = ack;
        pConn->Mss = mss; // save maximum segment size
        if( (long)(pConn->SndUna - pConn->Iss) > 0 ) // If SND.UNA > ISS (our SYN has been ACKed),
        {
          pConn->State = TCP_ESTAB; // change the connection state to ESTABLISHED,
          pConn->Notify->Connect( connNo ); // signal "connected"
          TcpEmptySegment( pConn, pConn->SndNxt, pConn->RcvNxt, TCP_ACK ); // form an ACK segment and send it.
        }
        else // Otherwise
        {
          pConn->State = TCP_SYN_RCVD; // enter SYN-RECEIVED,
          TcpSynSegment( pConn, pConn->Iss, pConn->RcvNxt, TCP_SYN | TCP_ACK ); // form a SYN,ACK segment and send it.
        }
        pConn->Ticks = 0; // restart timer
      }
      return;

  } // switch( pConn->State )

  // Otherwise,

  // first check sequence number
  if( len == 0 )
    if( pConn->RcvWnd == 0 )
      accept = seq == pConn->RcvNxt; // SEG.SEQ = RCV.NXT
    else
      accept = (long)(pConn->RcvNxt - seq) <= 0 && // RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND
               (long)(seq - (pConn->RcvNxt + pConn->RcvWnd)) < 0;
  else
    if( pConn->RcvWnd == 0 )
      accept = 0; // not acceptable
    else
      accept = ((long)(pConn->RcvNxt - seq) <= 0 && // RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND
                (long)(seq - (pConn->RcvNxt + pConn->RcvWnd)) < 0) ||
               ((long)(pConn->RcvNxt - (seq + len - 1) <= 0) && // or RCV.NXT =< SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND
                (long)((seq + len - 1) - (pConn->RcvNxt + pConn->RcvWnd)) < 0);

  // because there is not enough memory to store segments needed later,
  // we only accept segments with SEG.SEQ <= RCV.NXT
  // thus, we reject the segment if SEG.SEQ > RCV.NXT
  // BAD PERFORMANCE: this is _not_ a bug, but a major impact on performace when packets arrive out of order
  //  - however, we cannot do something against it,
  //    because there is not enough memory available on the controller
  //    to store segements for later processing
  if( accept && (long)(seq - pConn->RcvNxt) > 0 )
    accept = 0;

  // If an incoming segment is not acceptable
  if( ! accept )
  {
    /* disabled this - sometimes it ssems to generates endless ACKs being exchanged with remote host)
    if( ! (flags & TCP_RST) ) // (unless the RST bit is set)
      TcpEmptySegment( pConn, pConn->SndNxt, pConn->RcvNxt, TCP_ACK ); // an acknowledgment should be sent in reply */
    return; // drop the unacceptable segment and return.
  }

  // if segment contains duplicate data
  tmp = pConn->RcvNxt - seq; // cannot be negative because of checks above
  if( tmp > 0 )
  {
    // remove duplicate SYN flag from segment
    if( flags & TCP_SYN )
    {
      flags &= ~TCP_SYN;
      tmp--;
    }
    // remove duplicate data from segment
    ofs += tmp;
    len -= tmp; // length cannot become negative here because of checks above
    // now this segments starts with the expected sequence number
    seq = pConn->RcvNxt;
  }

  // if segment extends beyond end of receive window
  if( len > pConn->RcvWnd )
  {
    // remove data behind receive window
    seqEnd -= len - pConn->RcvWnd;
    len = pConn->RcvWnd;
  }

  // restart timer, because this was an acceptable segment
  pConn->Ticks = 0;

  // second check the RST bit,
  if( flags & TCP_RST )
  {
    switch( pConn->State )
    {

      case TCP_SYN_RCVD:
        pConn->State = TCP_CLOSED; // enter the CLOSED state and delete the TCB, an return.
        pConn->Notify->Close( connNo ); // signal "connection refused"
        return;

      case TCP_ESTAB:
      case TCP_FIN_WAIT_1:
      case TCP_FIN_WAIT_2:
      case TCP_CLOSE_WAIT:
        pConn->State = TCP_CLOSED; // Enter the CLOSED state, delete the TCB, and return.
        pConn->Notify->Close( connNo ); // signal "connection reset"
        return;

      case TCP_CLOSING:
      case TCP_LAST_ACK:
      case TCP_TIME_WAIT:
        pConn->State = TCP_CLOSED; // enter the CLOSED state, delete the TCB, and return.
        return;

    } // switch( pConn->State )
  } // if( flags & TCP_RST )

  // third check security and precedence
  //  - this does not apply to our implementation because we do not support security and precedence on TCP layer

  // fourth, check the SYN bit
  if( flags & TCP_SYN )
  {
    switch( pConn->State )
    {

      case TCP_SYN_RCVD:
      case TCP_ESTAB:
      case TCP_FIN_WAIT_1:
      case TCP_FIN_WAIT_2:
      case TCP_CLOSE_WAIT:
        // If the SYN is in the window it is an error,
        //  - this is always the case here, because SYN not in window would habe been removed above
        TcpEmptySegment( pConn, ack, 0, TCP_RST ); // send a reset,
        pConn->State = TCP_CLOSED; // enter the CLOSED state, delete the TCB, and return.
        pConn->Notify->Close( connNo ); // signal "connection reset"
        return;

      case TCP_CLOSING:
      case TCP_LAST_ACK:
      case TCP_TIME_WAIT:
        // If the SYN is in the window it is an error,
        //  - this is always the case here, because SYN not in window would habe been removed above
        TcpEmptySegment( pConn, ack, 0, TCP_RST ); // send a reset,
        pConn->State = TCP_CLOSED; // enter the CLOSED state, delete the TCB, and return.
        return;

    } // switch( pConn->State )
  } // if( flags & TCP_SYN )

  // fifth check the ACK field,
  if( ! (flags & TCP_ACK) ) // if the ACK bit is off
    return; // drop the segment and return
  switch( pConn->State )
  {

      case TCP_SYN_RCVD:
        if( (long)(pConn->SndUna - ack) <= 0 && // If SND.UNA =< SEG.ACK =< SND.NXT
            (long)(ack - pConn->SndNxt) <= 0 )
        {
          pConn->State = TCP_ESTAB; // then enter ESTABLISHED state
          pConn->Notify->Connect( connNo ); // signal "connected"
          // and continue processing.
        }
        else // If the segment acknowledgment is not acceptable,
        {
          TcpEmptySegment( pConn, ack, 0, TCP_RST ); // form a reset segment, and send it.
          return; // drop this segment
        }
        // no break here

      case TCP_ESTAB:
      case TCP_FIN_WAIT_1:
      case TCP_FIN_WAIT_2:
      case TCP_CLOSE_WAIT:
      case TCP_CLOSING:
        if( (long)(ack - pConn->SndUna) <= 0 ) // If the ACK is a duplicate (SEG.ACK <= SND.UNA),
          break; // it can be ignored.
        if( (long)(ack - pConn->SndNxt) > 0 ) // If the ACK acks something not yet sent (SEG.ACK > SND.NXT)
        {
          TcpEmptySegment( pConn, pConn->SndNxt, pConn->RcvNxt, TCP_ACK ); // then send an ACK,
          return; // drop the segment, and return.
        }
        // here: SND.UNA < SEG.ACK =< SND.NXT
        pConn->SndUna = ack; // set SND.UNA <- SEG.ACK.
        if( (long)(pConn->SndWl1 - seq) < 0 || // If SND.WL1 < SEG.SEQ
            (pConn->SndWl1 == seq && (long)(pConn->SndWl2 - ack) <= 0) ) // or (SND.WL1 = SEG.SEQ and SND.WL2 =< SEG.ACK),
        {
          pConn->SndWnd = wnd; // set SND.WND <- SEG.WND,
          pConn->SndWl1 = seq; // set SND.WL1 <- SEG.SEQ,
          pConn->SndWl2 = ack; // and set SND.WL2 <- SEG.ACK.
        }
        pConn->Timeout = 0; // restart timeout timer
        pConn->Notify->Sent( connNo, pConn->SndUna - (pConn->Iss + 1) ); // signal "send completed" (up to ack)
        // additional processing
        switch( pConn->State )
        {
          case TCP_FIN_WAIT_1:
            if( pConn->SndUna == pConn->SndNxt ) // if our FIN is now acknowledged
              pConn->State = TCP_FIN_WAIT_2; // then enter FIN-WAIT-2 and continue processing in that state.
            // no break here
          case TCP_FIN_WAIT_2:
            if( pConn->SndUna == pConn->SndNxt ) // if the retransmission queue is empty,
              pConn->Notify->Close( connNo ); // the user's CLOSE can be acknowledged
            break;
          case TCP_CLOSING:
            if( pConn->SndUna == pConn->SndNxt ) // if the ACK acknowledges our FIN
              pConn->State = TCP_TIME_WAIT; // then enter the TIME-WAIT state,
            else
              return; // otherwise ignore the segment.
            break;
        }
        break;

      case TCP_LAST_ACK:
        // The only thing that can arrive in this state is an acknowledgment of our FIN.
        if( pConn->SndUna == pConn->SndNxt ) // If our FIN is now acknowledged,
        {
          pConn->State = TCP_CLOSED; // delete the TCB, enter the CLOSED state,
          return; // and return.
        }
        break;

      case TCP_TIME_WAIT:
        // The only thing that can arrive in this state is a retransmission of the remote FIN.
        TcpEmptySegment( pConn, ack, seqEnd, TCP_ACK ); // Acknowledge it,
        break;

  } // switch( pConn->State )

  // sixth, check the URG bit,
  //  - this cannot occur, because we have alredy reset the connection if URG was set

  // no ACK needs to be sent yet
  sendAck = 0;

  // seventh, process the segment text,
  if( len > 0 )
  {
    switch( pConn->State )
    {

      case TCP_ESTAB:
      case TCP_FIN_WAIT_1:
      case TCP_FIN_WAIT_2:
        pConn->RcvWnd -= len; // make receive window smaller (len <= pConn->RcvWnd)
        pConn->RcvNxt += len; // advance sequence number of next data to receive
        pConn->Timeout = 0; // restart timeout timer
        pConn->RcvWnd = pConn->Notify->Received( connNo, pConn->RcvNxt - (pConn->Irs + 1) - len, // give received data to user
                                                 (unsigned char *)&pTcpPack->TcpHdr + ofs, len, // (update receive window size)
                                                 pConn->RcvWnd );
        sendAck = 1; // remember to send an ACK
        break;

    } // switch( pConn->State )
  } // if( len > 0 )

  // eighth, check the FIN bit,
  if( flags & TCP_FIN )
  {
    switch( pConn->State )
    {

      case TCP_SYN_RCVD:
      case TCP_ESTAB:
        sendAck = 1; // remember to send an ACK
        pConn->RcvNxt++; // FIN counts as one in sequence number space
        pConn->State = TCP_CLOSE_WAIT; // Enter the CLOSE-WAIT state.
        // close outbound part of the connection
        // i.e. do an automatic call to close
        TcpEmptySegment( pConn, pConn->SndNxt, pConn->RcvNxt, TCP_FIN | TCP_ACK ); // send a FIN
        pConn->SndNxt++;
        pConn->State = TCP_CLOSING;
        pConn->Notify->Close( connNo ); // signal "connection closed" to user
        return; // we do not need to send an ack, becaue we already sent it with our FIN

      case TCP_FIN_WAIT_1:
        sendAck = 1; // remember to send an ACK
        pConn->RcvNxt++; // FIN counts as one in sequence number space
        pConn->State = TCP_CLOSING; // Enter the CLOSING state.
        break;

      case TCP_FIN_WAIT_2:
        sendAck = 1; // remember to send an ACK
        pConn->RcvNxt++; // FIN counts as one in sequence number space
        pConn->State = TCP_TIME_WAIT; // Enter the TIME-WAIT state.
        break;

      case TCP_CLOSING:
      case TCP_CLOSE_WAIT:
      case TCP_LAST_ACK:
      case TCP_TIME_WAIT:
        sendAck = 1; // remember to send an ACK
        break;

    } // switch( pConn->State )
  }

  // send data / ACK segment
  TcpSendDataSegment( connNo, pConn, sendAck );
}

// open a TCP connection
// must not be called from a TCP notification function
// returns the connection number of the new connection of 0xFF in case of error
unsigned char TcpOpen( unsigned char * remoteIp, unsigned short remotePort, // (extern)
                       unsigned short initialWnd, struct TcpNotify * Notify )
#define TcpOpenLocalPortMin 32768
#define TcpOpenLocalPortRange 16384
{
  static unsigned short nextLocalPort = TcpOpenLocalPortMin; // local port to use for next TCP connection
  unsigned short localPort;
  unsigned char i;

  // search an unused local port
  for( localPort = nextLocalPort; ; localPort++ )
  {
    for( i = 0; i < count( TcpConns ); i++ )
      if( TcpConns[i].State != TCP_CLOSED &&
          TcpConns[i].LocalPort == localPort )
        break;
    if( i >= count( TcpConns ) )
      break;
  }
  // save next local port to use
  nextLocalPort = localPort + 1;
  if( nextLocalPort >= TcpOpenLocalPortMin + TcpOpenLocalPortRange )
    nextLocalPort = TcpOpenLocalPortMin;

  // search empty connection slot
  for( i = 0; i < count( TcpConns ); i++ )
    if( TcpConns[i].State == TCP_CLOSED )
      break;
  // no free connection slot found
  if( i >= count( TcpConns ) )
    return 0xFF;

  // create new connection in this slot
  ip_cpy( TcpConns[i].RemoteIp, remoteIp );
  TcpConns[i].LocalPort = localPort;
  TcpConns[i].RemotePort = remotePort;
  TcpConns[i].RcvNxt = 0;
  TcpConns[i].Irs = 0;
  RandomGetData( (unsigned char *)&TcpConns[i].Iss, sizeof( TcpConns[i].Iss ) ); // An initial send sequence number (ISS) is selected.
  TcpSynSegment( &TcpConns[i], TcpConns[i].Iss, 0, TCP_SYN ); // A SYN segment of the form <SEQ=ISS><CTL=SYN> is sent.
  TcpConns[i].SndUna = TcpConns[i].Iss; // Set SND.UNA to ISS,
  TcpConns[i].SndNxt = TcpConns[i].Iss + 1; // SND.NXT to ISS+1,
  TcpConns[i].State = TCP_SYN_SENT; // enter SYN-SENT state,
  TcpConns[i].SndWnd = 0; // not allowed to send data for now
  TcpConns[i].SndWl1 = 0; // window size was never updated
  TcpConns[i].SndWl2 = 0;
  TcpConns[i].Ticks = 0; // restart timers
  TcpConns[i].Timeout = 0;
  TcpConns[i].LifeTime = 0;
  TcpConns[i].RcvWnd = initialWnd;
  TcpConns[i].Notify = Notify;

  // return connection number
  return i;
}

// close a TCP connection
// must not be called from a TCP notification function
void TcpClose( unsigned char connNo ) // (extern)
{
  struct TcpConnection * pConn;

  // connection does not exist
  if( connNo >= count( TcpConns ) || TcpConns[connNo].State == TCP_CLOSED )
    return;

  // get connection
  pConn = &TcpConns[connNo];

  // different actions in different states accroding to RFC793
  switch( pConn->State  )
  {

    case TCP_LISTEN:
    case TCP_SYN_SENT:
      pConn->State = TCP_CLOSED; // Delete TCB, enter CLOSED state,
      break; // and return.

    case TCP_SYN_RCVD:
    case TCP_ESTAB:
      TcpEmptySegment( pConn, pConn->SndNxt, pConn->RcvNxt, TCP_FIN | TCP_ACK ); // form a FIN segment and send it,
      pConn->SndNxt++;
      pConn->State = TCP_FIN_WAIT_1; // and enter FIN-WAIT-1 state;
      pConn->Notify->Close( connNo ); // signal "connection closed" to user
      break;

    case TCP_CLOSE_WAIT:
      TcpEmptySegment( pConn, pConn->SndNxt, pConn->RcvNxt, TCP_FIN | TCP_ACK ); // send a FIN segment,
      pConn->SndNxt++;
      pConn->State = TCP_CLOSING; // enter CLOSING state.
      pConn->Notify->Close( connNo ); // signal "connection closed" to user
      break;

  } // switch( pConn->State  )
}

// request sending on a TCP connection
// must not be called from a TCP notification function
// this makes the send notification to be called if possible
void TcpSend( unsigned char connNo ) // (extern)
{
  struct TcpConnection * pConn;

  // connection does not exist
  if( connNo >= count( TcpConns ) || TcpConns[connNo].State == TCP_CLOSED )
    return;

  // get connection
  pConn = &TcpConns[connNo];

  // different behaviour in different states
  switch( pConn->State )
  {

    case TCP_ESTAB:
    case TCP_CLOSE_WAIT:
      // if nothing is sent and not yet ACKed
      if( pConn->SndUna == pConn->SndNxt )
        // send a data segment
        TcpSendDataSegment( connNo, pConn, 0 );
      break;

  } // switch( TcpConns[i].State )
}

// dummy notification functions
void TcpDummyConnect( unsigned char ConnNo ) { }
void TcpDummyClose( unsigned char ConnNo ) { }
unsigned short TcpDummySend( unsigned char ConnNo, unsigned long Pos, unsigned char * pBuffer, unsigned short MaxLen ) { return 0; }
void TcpDummySent( unsigned char ConnNo, unsigned long Pos ) { }
unsigned short TcpDummyReceived( unsigned char ConnNo, unsigned long Pos, unsigned char * pBuffer, unsigned short Len, unsigned short curWnd ) { return max( curWnd, 128 ); }
struct TcpNotify TcpDummyNotify = // (extern)
{
  .Connect = TcpDummyConnect,
  .Close = TcpDummyClose,
  .Send = TcpDummySend,
  .Sent = TcpDummySent,
  .Received = TcpDummyReceived,
};

