// ----------------------------------------------------------------------------
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
// ----------------------------------------------------------------------------

// #define DEBUG_TCP

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;

#if !SINGULARITY
using System.Net;
#endif

using Microsoft.Contracts;

using NetStack.Common;
using TcpError = NetStack.Contracts.TcpError;

namespace NetStack.Runtime
{
    using Protocols;

    public enum TcpStateEnum
    {
        Undefined   = 0,
        Closed      = 1,
        Listen      = 2,
        SynRecvd    = 3,
        SynSent     = 4,
        Established = 5,
        CloseWait   = 6,
        LastAck     = 7,
        FinWait1    = 8,
        FinWait2    = 9,
        Closing    = 10,
    }

    internal class TcpState
    {
        /// <summary>
        /// Names corresponding to Enums, above (reflection missing).
        /// </summary>
        internal static readonly string[] TcpStateNames = new string[] {
            "Undefined",
            "Closed   ",
            "Listen   ",
            "SynRecvd ",
            "SynSent  ",
            "Establish",
            "CloseWait",
            "LastAck  ",
            "FinWait1 ",
            "FinWait2 ",
            "Closing  ",
        };

        private static TcpState! instance = new TcpState();

        internal static TcpState! InstanceOfUndefined()
        {
            return TcpState.instance;
        }

        // ctor
        internal TcpState()
        {
        }

        // make a state transition
        protected void ChangeState(TcpSession! tcpSession, TcpState newState)
        {
            tcpSession.ChangeState((!)newState);
        }

        // enter the new state
        virtual internal void OnStateEnter(TcpSession! tcpSession)
        {
        }

        // a new packet(=event) received, should handle it
        // according to the current state.
        virtual internal NetStatus OnPacketReceive(TcpSession! tcpSession,
                                                   NetPacket! packet,
                                                   object     context)
        {
            return NetStatus.Code.PROTOCOL_OK;
        }

        // leave the current state to the new one
        virtual internal void OnStateLeave(TcpSession! tcpSession, TcpState newState)
        {
        }

        // Handle timeout events.
        // args is a session argument which is session specific
        virtual internal NetStatus OnTimeout(TcpSession! tcpSession, Dispatcher.CallbackArgs args)
        {
            TcpSessionEventsSource.EventLog.LogTimeout(
                tcpSession.Uid,
                tcpSession.currentState.StateEnum,
                TcpSession.TcpTimeoutType.Unknown);

            return NetStatus.Code.PROTOCOL_OK;
        }

        // Get the state name for debugging
        internal string GetStateName()
        {
            return TcpState.TcpStateNames[(int)this.StateEnum];
        }

        // get state enum for eventing (tracing)
        virtual internal TcpStateEnum GetStateEnum()
        {
            return TcpStateEnum.Undefined;
        }

        // State's Enumeration as a Property
        internal TcpStateEnum StateEnum
        {
            get { return this.GetStateEnum(); }
        }

        // State's Name as a Property
        internal string StateName
        {
            get { return TcpState.TcpStateNames[(int)this.StateEnum]; }
        }

        [ Conditional("DEBUG_TCP") ]
        internal static void DebugPrint(string format, params object [] args)
        {
            Core.Log(format, args);
        }

        [ Conditional("DEBUG_TCP") ]
        internal static void DebugPrintLine(string format, params object [] args)
        {
            TcpState.DebugPrint(format, args);
        }
    }

    ///
    // This the TCP Finite State Machine Implementation
    // NOTICE: This is just a quick implementation...
    // should handle FIN, RST, window management, slow start, double acks...
    // 
    internal class TCPFSM
    {

        // Definition of the TCP states
        internal static TcpState! CLOSED { get {return TCPStateClosed.Instance();}}

        internal static TcpState! LISTEN { get {return TCPStateListen.Instance();}}
        internal static TcpState! SYN_RECVD { get {return TCPStateSynRcv.Instance();}}
        internal static TcpState! SYN_SENT  { get {return TCPStateSynSent.Instance();}}
        internal static TcpState! ESTABLISHED  { get {return TCPStateEstab.Instance();}}
        internal static TcpState! CLOSE_WAIT  { get {return TCPCloseWait.Instance();}}
        internal static TcpState! LAST_ACK  { get {return TCPLastAck.Instance();}}

        internal static TcpState! FIN_WAIT1 { get {return TCPFINWait1.Instance();}}
        internal static TcpState! FIN_WAIT2 { get {return TCPFINWait2.Instance();}}
        internal static TcpState! CLOSING { get {return TCPClosing.Instance();}}

        //----------------------------------------------------------------------------

        // Compare two TCP sequence numbers:
        //  - means A < B, 0 means A == B and + means A > B
        [Pure]
        [Inline]
        private static int TcpSequenceNumberCompare(uint seqA, uint seqB)
        {
            // Exploit integer underflow to compare correctly even in the case
            // of sequence number wraparound. This assumes the two numbers are
            // always in the same half of the numberspace.

            return unchecked((int)(unchecked(seqA - seqB)));
        }

        [Pure]
        internal static bool TCPSeqEQ(uint seqA, uint seqB)
        {
            return TCPFSM.TcpSequenceNumberCompare(seqA, seqB) == 0;
        }

        [Pure]
        internal static bool TCPSeqNEQ(uint seqA, uint seqB)
        {
            return TCPFSM.TcpSequenceNumberCompare(seqA, seqB) != 0;
        }

        [Pure]
        internal static bool TCPSeqLEQ(uint seqA, uint seqB)
        {
            return TCPFSM.TcpSequenceNumberCompare(seqA, seqB) <= 0;
        }

        [Pure]
        internal static bool TCPSeqLess(uint seqA, uint seqB)
        {
            return TCPFSM.TcpSequenceNumberCompare(seqA, seqB) < 0;
        }

        [Pure]
        internal static bool TCPSeqGEQ(uint seqA, uint seqB)
        {
            return TCPFSM.TcpSequenceNumberCompare(seqA, seqB) >= 0;
        }

        [Pure]
        internal static bool TCPSeqGreater(uint seqA, uint seqB)
        {
            return TCPFSM.TcpSequenceNumberCompare(seqA, seqB) >  0;
        }

        //----------------------------------------------------------------------------
        internal sealed class TCPStateClosed : TcpState
        {
            static TCPStateClosed! instance = new TCPStateClosed();
            static TCPStateClosed() {}

            internal static TCPStateClosed! Instance()
            {
                return instance;
            }

            // get state enum for eventing (tracing) and debugging.
            override internal TcpStateEnum GetStateEnum()
            {
                return TcpStateEnum.Closed;
            }

            // enter the Closed state
            override internal void OnStateEnter(TcpSession! tcpSession)
            {
                tcpSession.DestroyRetransmitTimer();
            }

            // leave the state
            override internal void OnStateLeave(TcpSession! tcpSession, TcpState newState)
            {
                assert newState != null;
                base.OnStateLeave(tcpSession, newState);
                switch (newState.StateEnum) {
                    case TcpStateEnum.Listen:
                        break;
                    case TcpStateEnum.SynSent:
                        if (!TCPFSM.SendSYN(tcpSession, TcpFormat.TCP_MSS)) {
                            // This should never happen; our outbound
                            // packet queue should never be empty
                            // while we're setting up a session
                            Debug.Assert(false);
                        }
                        break;
                    case TcpStateEnum.SynRecvd:
                        break;
                    default:
                        Core.Panic("TCP: Ses{0,3} ({1}) state machine error!",
                                   tcpSession.Uid, this.StateName);
                        break;
                }
            }

            override internal NetStatus OnTimeout(TcpSession! tcpSession, Dispatcher.CallbackArgs args)
            {
                base.OnTimeout(tcpSession, args);
                return NetStatus.Code.PROTOCOL_OK;
            }

            override internal NetStatus OnPacketReceive(TcpSession! tcpSession,
                                                        NetPacket!  packet,
                                                        object      context)
            {
                // A Closed session cannot handle any packets.
                // BUGBUG: The parent class should make this the default behavior. // TODO
                return NetStatus.Code.PROTOCOL_DROP_ERROR;
            }
        }

        //----------------------------------------------------------------------------
        // a passive connection listen state
        internal sealed class TCPStateListen : TcpState
        {
            static TCPStateListen! instance = new TCPStateListen();
            static TCPStateListen() {}

            internal static TCPStateListen! Instance()
            {
                return instance;
            }

            // get state enum for eventing (tracing) and debugging.
            override internal TcpStateEnum GetStateEnum()
            {
                return TcpStateEnum.Listen;
            }

            override internal void OnStateEnter(TcpSession! tcpSession) {
                DebugPrintLine("TCP: Ses{0,3} ({1}) Waiting for connections.",
                               tcpSession.Uid, this.StateName);
            }

            override internal NetStatus OnPacketReceive(TcpSession! tcpSession,
                                                        NetPacket! packet,
                                                        object context)
            {
                assert context != null;
                TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)context;

                if (TcpFormat.IsReset(ref tcpHeader)) {
                    return NetStatus.Code.PROTOCOL_DROP_ERROR;  // we ignore it
                }

                if (TcpFormat.IsAck(ref tcpHeader)) {
                    SendReset(tcpSession, false);
                    return NetStatus.Code.PROTOCOL_DROP_ERROR;
                }

                if (TcpFormat.IsSync(ref tcpHeader)) {
                    // we received a syn!
                    // create a new TCP session and initialize it (only if
                    // we have room)  TBC: should use listen param for bound)
                    // new session's new state (OnEnter) will send syn-ack
                    //  (this is the result of the ChangeState call).

                    TcpSession client =
                        (TcpSession!)tcpSession.Protocol.CreateSession();
                    client.passiveSession = tcpSession;  // this is our "owner"
                    client.SetLocalEndPoint(tcpSession.LocalAddress,
                                            tcpSession.LocalPort);
                    client.InitializeServerSession(tcpHeader.seq,
                                                   tcpHeader.window);

                    // we have access to the IP header from the packet overlapped
                    // context (TCPModule put it there)
                    IPFormat.IPHeader ipHeader =
                        packet.OverlapContext as IPFormat.IPHeader;
                    assert ipHeader != null;

                    client.SetRemoteEndPoint(ipHeader.Source,
                                             tcpHeader.sourcePort);

                    // Save ourselves work if we should reject the session right away
                    if (tcpSession.AcceptQueueIsFull()) {
                        TcpSegment reset = CreateResetSegment(client, true);
                        tcpSession.Protocol.OnProtocolSend(reset);
                        return NetStatus.Code.PROTOCOL_DROP_ERROR;
                    }

                    // New session starts at syn-rcv.  It immediately (from
                    //  OnEntry) sends a SYN-Ack and starts the connect timer.
                    client.ChangeState(SYN_RECVD);

                    // WE are left at the SAME state!
                    return NetStatus.Code.PROTOCOL_OK;
                }
                else {
                    return NetStatus.Code.PROTOCOL_DROP_ERROR;
                }
            }
        }

        //----------------------------------------------------------------------------
        internal sealed class TCPStateSynRcv : TcpState
        {
            static TCPStateSynRcv! instance = new TCPStateSynRcv();
            static TCPStateSynRcv() {}

            internal static TCPStateSynRcv! Instance()
            {
                return instance;
            }

            // get state enum for eventing (tracing)
            override internal TcpStateEnum GetStateEnum()
            {
                return TcpStateEnum.SynRecvd;
            }

            override internal void OnStateEnter(TcpSession! tcpSession)
            {
                // send syn-ack
                if (!SendSYNAck(tcpSession)) {
                    // This should never happen; our outbound packet queues
                    // should never be full while we're setting up a session.
                    Debug.Assert(false);
                }

                // Start timer to limit the time to establishing the new session
                tcpSession.StartConnectTimer();
            }

            override internal NetStatus OnPacketReceive(TcpSession! tcpSession,
                                                        NetPacket! packet,
                                                        object context)
            {
                assert context != null;
                // all TCP states get the tcpHeader context
                TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)context;
                uint segmentSize = (uint)packet.Available;

                bool packetIsAcceptable =
                    IsSegmentAcceptable(tcpSession, ref tcpHeader, segmentSize);

                if (!packetIsAcceptable) {
                    if (!TcpFormat.IsReset(ref tcpHeader)) {
                        // send an ACK; don't care about whether this works
                        //  or not since we're discarding the packet anyhow.
                        TCPFSM.SendAck(tcpSession);
                    }
                    return NetStatus.Code.PROTOCOL_DROP_ERROR;
                }

                // we accept the segment
                Debug.Assert(tcpHeader.seq == tcpSession.sessionTCB.RCV.NXT);

                // if we get a reset
                if (TcpFormat.IsReset(ref tcpHeader)) {
                    NetStatus.Code result;
                    switch (tcpSession.oldStateEnum) {
                        case TcpStateEnum.Listen:
                            // if we came from the listen state (passive open)
                            // the reset will return us to the Listen state
                            tcpSession.FlushRetransmissions();
                            ChangeState(tcpSession, LISTEN);
                            result = NetStatus.Code.PROTOCOL_DROP_ERROR;
                            break;
                        case TcpStateEnum.SynSent:
                            // we came from SYN_SENT (active open)
                            // the connection was refused, close it!
                            HandleTerminateSession(tcpSession, null, TcpError.Refused);
                            result = NetStatus.Code.PROTOCOL_DROP_ERROR;
                            break;
                        default:
                            result = NetStatus.Code.PROTOCOL_DROP_ERROR;
                            break;
                    }

                    return result;
                }

                if (TcpFormat.IsSync(ref tcpHeader)) {
                    // reset
                    HandleTerminateSession(tcpSession,
                                           CreateResetSegment(tcpSession, false),
                                           TcpError.ProtocolViolation);
                    return NetStatus.Code.PROTOCOL_DROP_ERROR;
                }

                if (TcpFormat.IsAck(ref tcpHeader)) {
                    if (TCPSeqLEQ(tcpSession.sessionTCB.SND.UNA, tcpHeader.ackSeq) &&
                        TCPSeqGEQ(tcpSession.sessionTCB.SND.NXT, tcpHeader.ackSeq))
                    {
                        // enter established state

                        // we get an Ack for our syn-ack
                        // remove the SYN-ACK from the retransmit Q
                        TCPFSM.HandleTCPAck(tcpSession, ref tcpHeader);
                        AttemptToEnterESTABLISHED(tcpSession, ref tcpHeader);
                        return NetStatus.Code.PROTOCOL_OK;
                    }
                    else {
                        // reset
                        HandleTerminateSession(tcpSession,
                                               CreateResetSegment(tcpSession, false),
                                               TcpError.Reset);
                        return NetStatus.Code.PROTOCOL_DROP_ERROR;
                    }
                }

                if (TcpFormat.IsFIN(ref tcpHeader)) {
                    DebugPrintLine("TCP: Ses{0,3} ({1)} TCPStateSynRcv: Received FIN.",
                                   tcpSession.Uid, this.StateName);

                    // RCV.NXT should reflect the data that we processed out of
                    // this packet, but not the FIN. Advance over the FIN.
                    tcpSession.sessionTCB.RCV.NXT += 1;

                    // ack the FIN. Don't worry if this doesn't actually work;
                    // the remote side will think the ACK got lost and retransmit,
                    // which we will hopefully be able to successfully ACK later.
                    SendAck(tcpSession);
                    ChangeState(tcpSession, CLOSE_WAIT);
                }

                return NetStatus.Code.PROTOCOL_DROP_ERROR;
            }
        }

        //----------------------------------------------------------------------------
        internal sealed class TCPStateSynSent : TcpState
        {
            static TCPStateSynSent! instance = new TCPStateSynSent();
            static TCPStateSynSent() {}

            internal static TCPStateSynSent! Instance()
            {
                return instance;
            }

            // get state enum for eventing (tracing) and debugging.
            override internal TcpStateEnum GetStateEnum()
            {
                return TcpStateEnum.SynSent;
            }

            override internal void OnStateEnter(TcpSession! tcpSession)
            {
                DebugPrintLine("TCP: Ses{0,3} ({1}) Sending SYN.",
                               tcpSession.Uid, this.StateName);
            }

            // we sent a syn we wait for syn-ack
            override internal NetStatus OnPacketReceive(TcpSession! tcpSession,
                                                        NetPacket! packet,
                                                        object context)
            {
                assert context != null;
                // all TCP states get the tcpHeader context
                TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)context;
                bool ackAcceptable=false;

                if (TcpFormat.IsAck(ref tcpHeader)) {
                    if (TCPSeqLEQ(tcpHeader.ackSeq, tcpSession.sessionTCB.SND.ISS) ||
                        TCPSeqGreater(tcpHeader.ackSeq, tcpSession.sessionTCB.SND.NXT))
                    {
                        if (TcpFormat.IsReset(ref tcpHeader)) {
                            return NetStatus.Code.PROTOCOL_DROP_ERROR;
                        }

                        SendReset(tcpSession, false);
                        return NetStatus.Code.PROTOCOL_DROP_ERROR;
                    }

                    if (TCPSeqLEQ(tcpSession.sessionTCB.SND.UNA, tcpHeader.ackSeq) &&
                        TCPSeqLEQ(tcpHeader.ackSeq, tcpSession.sessionTCB.SND.NXT))
                    {
                        ackAcceptable=true;
                    }
                }

                if (TcpFormat.IsReset(ref tcpHeader)) {
                    if (ackAcceptable) {
                        // connection was reset, drop the segment
                        // and close the connection.
                        HandleTerminateSession(tcpSession, null, TcpError.Reset);
                        return NetStatus.Code.PROTOCOL_DROP_ERROR;
                    }
                    else {
                        return NetStatus.Code.PROTOCOL_DROP_ERROR;
                    }
                }

                // check syn
                // ack is is ok or there is no ack and no RST
                if (TcpFormat.IsSync(ref tcpHeader)) {
                    if (ackAcceptable) {
                        DebugPrintLine("TCP: Ses{0,3} ({1}) SYNSENT, Received SYN-ACK",
                                       tcpSession.Uid, this.StateName);

                        // use the ack parameters to complete the session's data
                        tcpSession.sessionTCB.RCV.NXT = tcpHeader.seq+1;
                        tcpSession.sessionTCB.RCV.IRS = tcpHeader.seq;
                        tcpSession.sessionTCB.SND.UNA = tcpHeader.ackSeq;
                        tcpSession.sessionTCB.SND.WND = tcpHeader.window;
                        tcpSession.sessionTCB.RCV.WND = TcpFormat.TCP_MSS;

                        // now remove the SYN from the retransmit Q
                        // (so we don't retransmit it again)
                        TCPFSM.HandleTCPAck(tcpSession, ref tcpHeader);

                        if (tcpSession.sessionTCB.SND.UNA > tcpSession.sessionTCB.SND.ISS) {
                            // our syn has been acked.
                            TCPFSM.SendAck(tcpSession);

                            // change the state to established!
                            AttemptToEnterESTABLISHED(tcpSession, ref tcpHeader);
                            return NetStatus.Code.PROTOCOL_OK;
                        }
                    }
                    else {
                        // received a new SYN (overlap connection)
                        // (see SYN_RECVD for more details)

                        // Create a new object to track the new session
                        TcpSession client =
                            (TcpSession!) (tcpSession.Protocol.CreateSession());
                        client.passiveSession = tcpSession; // this is our "owner"
                        client.SetLocalEndPoint(tcpSession.LocalAddress,
                                                tcpSession.LocalPort);
                        client.InitializeServerSession(tcpHeader.seq,
                                                       tcpHeader.window);

                        // we have access to the IP header from the packet overlapped
                        // context (TCPModule put it there)
                        IPFormat.IPHeader ipHeader = packet.OverlapContext as IPFormat.IPHeader;
                        assert ipHeader!=null;
                        client.SetRemoteEndPoint(ipHeader.Source,
                                                 tcpHeader.sourcePort);

                        // Save ourselves work if we should reject the session right away
                        if (tcpSession.AcceptQueueIsFull()) {
                            TcpSegment reset = CreateResetSegment(client, true);
                            tcpSession.Protocol.OnProtocolSend(reset);
                            return NetStatus.Code.PROTOCOL_DROP_ERROR;
                        }

                        // New session starts at syn-rcv.  It immediately (from
                        //  OnEntry) sends a SYN-Ack and starts the connect timer.
                        client.ChangeState(SYN_RECVD);

                        return NetStatus.Code.PROTOCOL_OK;
                    }
                }
                return NetStatus.Code.PROTOCOL_DROP_ERROR;
            }
        } // TCPStateSynSent   

        //----------------------------------------------------------------------------
        // This is the working mode state
        internal sealed class TCPStateEstab : TcpState
        {
            static TCPStateEstab! instance = new TCPStateEstab();
            static TCPStateEstab() {}

            internal static TCPStateEstab! Instance()
            {
                return instance;
            }

            // get state enum for eventing (tracing) and debugging.
            override internal TcpStateEnum GetStateEnum()
            {
                return TcpStateEnum.Established;
            }

            override internal void OnStateEnter(TcpSession! tcpSession)
            {
                // When becoming Established, stop the connect timer
                // if there is one ticking.
                tcpSession.DestroyConnectTimer();

                // Wake up anyone waiting for the connection to complete
                tcpSession.setupCompleteEvent.Set();
            }

            override internal NetStatus OnPacketReceive(TcpSession! tcpSession,
                                                        NetPacket! packet,
                                                        object context)
            {
                assert context != null;
                TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)context;
                uint segmentSize = (uint)packet.Available;  // TCPModule already shrinked it for us

                NetStatus res = NetStatus.Code.PROTOCOL_DROP_ERROR;

                bool accept=IsSegmentAcceptable(tcpSession, ref tcpHeader, segmentSize);

                if (!accept) {
                    if (!TcpFormat.IsReset(ref tcpHeader)) {
                        // send an ACK and return
                        TCPFSM.SendAck(tcpSession);
                    }
                    return res;
                }

                if (TcpFormat.IsReset(ref tcpHeader)) {
                    // for simplicity, just close the connection.
                    HandleTerminateSession(tcpSession, null, TcpError.Reset);
                    return res;
                }

                // check syn
                if (TcpFormat.IsSync(ref tcpHeader)) {
                    // send a reset
                    HandleTerminateSession(tcpSession, CreateResetSegment(tcpSession, false), TcpError.Reset);
                    return res;
                }

                res = HandleTCPData(ref tcpHeader, packet, tcpSession);

                // our peer wants to end the relationship...
                if (TcpFormat.IsFIN(ref tcpHeader)) {
                    // RCV.NXT should reflect any data in this packet, but not the FIN.
                    // Advance over the FIN.
                    tcpSession.sessionTCB.RCV.NXT += 1;

                    // ack the FIN
                    SendAck(tcpSession);
                    ChangeState(tcpSession, CLOSE_WAIT);
                }

                return res;
            }
        }
        //----------------------------------------------------------------------------
        // CLOSE_WAIT is entered when we receive (and ACK) a FIN from the
        // remote side. There is no further data to receive, but we can
        // continue sending if we like.
        internal sealed class TCPCloseWait : TcpState
        {
            static TCPCloseWait! instance = new TCPCloseWait();
            static TCPCloseWait() {}

            internal static TCPCloseWait! Instance()
            {
                return instance;
            }

            // get state enum (debug)
            override internal TcpStateEnum GetStateEnum()
            {
                return TcpStateEnum.CloseWait;
            }

            override internal void OnStateEnter(TcpSession! tcpSession)
            {
                base.OnStateEnter(tcpSession);

                // Signal that there is nothing more to read on this
                // connection
                tcpSession.ValidForRead = false;
            }

            override internal NetStatus OnPacketReceive(TcpSession! tcpSession,
                                                        NetPacket! packet,
                                                        object context)
            {
                assert context != null;
                TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)context;
                uint segmentSize = (uint)packet.Available;

                if (!IsSegmentAcceptable(tcpSession, ref tcpHeader, segmentSize)) {
                    if (!TcpFormat.IsReset(ref tcpHeader)) {
                        // send an ACK and return
                        TCPFSM.SendAck(tcpSession);
                    }

                    return NetStatus.Code.PROTOCOL_DROP_ERROR;
                }

                if (TcpFormat.IsReset(ref tcpHeader) ||
                    TcpFormat.IsSync(ref tcpHeader) ||
                    (!TcpFormat.IsAck(ref tcpHeader)))
                {
                    // Abort
                    HandleTerminateSession(tcpSession, null, TcpError.Reset);
                    return NetStatus.Code.PROTOCOL_DROP_ERROR;
                }

                TCPFSM.HandleTCPAck(tcpSession, ref tcpHeader);
                return NetStatus.Code.PROTOCOL_OK;
            }
        }

        //----------------------------------------------------------------------------
        // LAST_ACK is entered when we close our side of the duplex
        // connection and the remote site had sent us its FIN
        // previously. We send out our own FIN and just hang around
        // to make sure it was received properly before shutting down.
        //
        // CALLER SHOULD ARRANGE TO TRANSMIT THE FIN PACKET BEFORE
        // ENTERING THIS STATE!
        internal sealed class TCPLastAck : TcpState
        {
            static TCPLastAck! instance = new TCPLastAck();
            static TCPLastAck() {}

            internal static TCPLastAck! Instance()
            {
                return instance;
            }

            // get state enum for eventing (tracing) and debugging.
            override internal TcpStateEnum GetStateEnum()
            {
                return TcpStateEnum.LastAck;
            }

            override internal void OnStateEnter(TcpSession! tcpSession)
            {
                base.OnStateEnter(tcpSession);

                // Flag that writing is now disallowed on this session
                tcpSession.ValidForWrite = false;
            }

            override internal NetStatus OnPacketReceive(TcpSession! tcbSession,
                                                        NetPacket! packet,
                                                        object context)
            {
                assert context != null;
                TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)context;
                uint segmentSize = (uint)packet.Available;  // TCPModule already shrinked it for us

                // RST is illegal at this point
                if (TcpFormat.IsReset(ref tcpHeader)) {
                    // for simplicity, just close the connection.
                    HandleTerminateSession(tcbSession, null, TcpError.Reset);
                    return NetStatus.Code.PROTOCOL_DROP_ERROR;
                }

                // SYNC causes us to issue a RST and abort
                if (TcpFormat.IsSync(ref tcpHeader)) {
                    // send a reset
                    HandleTerminateSession(tcbSession,
                                           CreateResetSegment(tcbSession, false),
                                           TcpError.Reset);
                    return NetStatus.Code.PROTOCOL_DROP_ERROR;
                }

                if (!IsSegmentAcceptable(tcbSession, ref tcpHeader, segmentSize)) {
                    // Just drop it
                    return NetStatus.Code.PROTOCOL_DROP_ERROR;
                }

                // Do ACK housekeeping
                if (TCPSeqLess(tcbSession.sessionTCB.SND.UNA, tcpHeader.ackSeq) &&
                    TCPSeqLEQ(tcpHeader.ackSeq, tcbSession.sessionTCB.SND.NXT))
                {
                    // This appears to be ACKing something we sent.
                    tcbSession.sessionTCB.SND.UNA = tcpHeader.ackSeq;

                    // remove the packet(s) from the retransmit queue
                    TCPFSM.HandleTCPAck(tcbSession, ref tcpHeader);
                }

                // Note the weirdness here; because we prepacketize
                // data, we don't want to compare to SND.NXT
                if (TCPSeqGEQ(tcbSession.sessionTCB.SND.UNA, tcbSession.sessionTCB.SND.NextSeq)) {
                    // The remote site has acknowledged everything we sent.
                    // We're done.
                    HandleTerminateSession(tcbSession, null, TcpError.Reset);
                }

                return NetStatus.Code.PROTOCOL_DROP_ERROR;
            }
        }

        //----------------------------------------------------------------------------
        // FIN_WAIT1 is entered when we close our side of the duplex
        // connection. We don't send any more data, but we continue to
        // receive data from the remote side.
        //
        // CALLER SHOULD ARRANGE TO SEND THE FIN PACKET BEFORE ENTERING
        // THIS STATE!
        internal sealed class TCPFINWait1 : TcpState
        {
            static TCPFINWait1! instance = new TCPFINWait1();
            static TCPFINWait1() {}

            internal static TCPFINWait1! Instance()
            {
                return instance;
            }

            // get state enum (debug)
            override internal TcpStateEnum GetStateEnum()
            {
                return TcpStateEnum.FinWait1;
            }

            override internal void OnStateEnter(TcpSession! tcpSession)
            {
                base.OnStateEnter(tcpSession);

                // Flag that writing is now disallowed on this session
                tcpSession.ValidForWrite = false;
            }

            // leave the state
            override internal void OnStateLeave(TcpSession! tcpSession, TcpState newState)
            {
                assert newState != null;
                base.OnStateLeave(tcpSession, newState);
            }

            override internal NetStatus OnPacketReceive(TcpSession! tcpSession, NetPacket! packet, object context)
            {
                assert context != null;
                TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)context;
                uint segmentSize = (uint)packet.Available;  // TCPModule already shrinked it for us

                // RST is illegal at this point
                if (TcpFormat.IsReset(ref tcpHeader)) {
                    // for simplicity, just close the connection.
                    HandleTerminateSession(tcpSession, null, TcpError.ProtocolViolation);
                    return NetStatus.Code.PROTOCOL_DROP_ERROR;
                }

                // SYNC causes us to issue a RST and abort
                if (TcpFormat.IsSync(ref tcpHeader)) {
                    // send a reset
                    HandleTerminateSession(tcpSession, CreateResetSegment(tcpSession, false), TcpError.ProtocolViolation);
                    return NetStatus.Code.PROTOCOL_DROP_ERROR;
                }

                if (!IsSegmentAcceptable(tcpSession, ref tcpHeader, segmentSize)) {
                    if (!TcpFormat.IsReset(ref tcpHeader)) {
                        // send an ACK and return
                        TCPFSM.SendAck(tcpSession);
                    }

                    return NetStatus.Code.PROTOCOL_DROP_ERROR;
                }

                // Chew on the payload...
                NetStatus retval = HandleTCPData(ref tcpHeader, packet, tcpSession);

                if (TCPSeqLEQ(tcpHeader.seq, tcpSession.sessionTCB.RCV.NXT) &&
                    (TcpFormat.IsFIN(ref tcpHeader)))
                {
                    // This is the remote side's FIN, and we have
                    // received all data preceding it, too!
                    // advance RCV.NXT over the FIN sequence
                    tcpSession.sessionTCB.RCV.NXT += 1;

                    // Note the weirdness here; because we
                    // prepacketize data, we don't want to test
                    // against SND.NXT
                    if (TCPSeqLess(tcpSession.sessionTCB.SND.UNA,
                                   tcpSession.sessionTCB.SND.NextSeq)) {
                        // ...but the remote side hasn't received
                        // everything we have said, yet.

                        // ack the FIN
                        SendAck(tcpSession);
                        ChangeState(tcpSession, CLOSING);
                    }
                    else {
                        // The remote side has heard everything we
                        // have to say. We're done.
                        // ACK the FIN and shut down
                        TcpSegment ackSeg = CreateAckSegment(tcpSession);
                        HandleTerminateSession(tcpSession, ackSeg, TcpError.Closed);
                    }
                }
                else {
                    // Note the weirdness here; because we
                    // prepacketize data, we do not want to test
                    // against SND.NXT, but rather SND.NextSeq.
                    if (TCPSeqGEQ(tcpSession.sessionTCB.SND.UNA,
                                  tcpSession.sessionTCB.SND.NextSeq)) {
                        // The remote side has ACKed everything we have
                        // said, but they haven't FINed themselves.
                        ChangeState(tcpSession, FIN_WAIT2);
                    }
                }

                return retval;
            }
        }

        //----------------------------------------------------------------------------
        // FIN_WAIT2 is entered when the other side has received our FIN, but has
        // more data to send. We wait for them to signal FIN as well.
        internal sealed class TCPFINWait2 : TcpState
        {
            static TCPFINWait2! instance = new TCPFINWait2();
            static TCPFINWait2() {}

            internal static TCPFINWait2! Instance()
            {
                return instance;
            }

            // get state enum (debug)
            override internal TcpStateEnum GetStateEnum()
            {
                return TcpStateEnum.FinWait2;
            }

            // leave the state
            override internal void OnStateLeave(TcpSession! tcpSession, TcpState newState)
            {
                base.OnStateLeave(tcpSession, newState);
            }

            override internal NetStatus OnPacketReceive(TcpSession! tcpSession,
                                                        NetPacket! packet,
                                                        object context)
            {
                assert context != null;
                TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)context;
                uint segmentSize = (uint)packet.Available;  // TCPModule already shrinked it for us

                // RST is illegal at this point
                if (TcpFormat.IsReset(ref tcpHeader)) {
                    // for simplicity, just close the connection.
                    HandleTerminateSession(tcpSession,
                                           null,
                                           TcpError.ProtocolViolation);
                    return NetStatus.Code.PROTOCOL_DROP_ERROR;
                }

                // SYNC causes us to issue a RST and abort
                if (TcpFormat.IsSync(ref tcpHeader)) {
                    // send a reset
                    HandleTerminateSession(tcpSession,
                                           CreateResetSegment(tcpSession, false),
                                           TcpError.ProtocolViolation);
                    return NetStatus.Code.PROTOCOL_DROP_ERROR;
                }

                if (!IsSegmentAcceptable(tcpSession, ref tcpHeader, segmentSize)) {
                    if (!TcpFormat.IsReset(ref tcpHeader)) {
                        // send an ACK and return
                        TCPFSM.SendAck(tcpSession);
                    }

                    return NetStatus.Code.PROTOCOL_DROP_ERROR;
                }

                // Chew on the payload...
                NetStatus retval = HandleTCPData(ref tcpHeader, packet, tcpSession);

                // Only check for FIN if we have previously received
                // all data.
                if (TCPSeqLEQ(tcpHeader.seq, tcpSession.sessionTCB.RCV.NXT) &&
                    (TcpFormat.IsFIN(ref tcpHeader)))
                {
                    // RCV.NXT should reflect the data in this packet, but not
                    // the FIN. Advance over the FIN...
                    tcpSession.sessionTCB.RCV.NXT += 1;

                    // ack the FIN and shut down
                    TcpSegment ackSeg = CreateAckSegment(tcpSession);
                    HandleTerminateSession(tcpSession, ackSeg, TcpError.Closed);
                }

                return retval;
            }
        }

        //----------------------------------------------------------------------------
        // CLOSING is entered when both sides have sent a FIN, but we're not sure
        // the other side has received all our data yet. We hang around processing
        // ACKs until we're sure the other side has heard everything we have to say.
        internal sealed class TCPClosing : TcpState
        {
            static TCPClosing! instance = new TCPClosing();
            static TCPClosing() {}

            internal static TCPClosing! Instance()
            {
                return instance;
            }

            // get state enum (debug)
            override internal TcpStateEnum GetStateEnum()
            {
                return TcpStateEnum.Closing;
            }

            override internal void OnStateEnter(TcpSession! tcpSession)
            {
                base.OnStateEnter(tcpSession);

                // Signal that there's nothing more to read on this session
                tcpSession.ValidForRead = false;
            }

            // leave the state
            override internal void OnStateLeave(TcpSession! tcpSession, TcpState newState)
            {
                assert newState != null;
                base.OnStateLeave(tcpSession, newState);
            }

            override internal NetStatus OnPacketReceive(TcpSession! tcpSession,
                                                        NetPacket! packet,
                                                        object context)
            {
                assert context != null;
                TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)context;
                uint segmentSize = (uint)packet.Available;  // TCPModule already shrinked it for us

                // RST is illegal at this point
                if (TcpFormat.IsReset(ref tcpHeader)) {
                    // for simplicity, just close the connection.
                    HandleTerminateSession(tcpSession, null, TcpError.ProtocolViolation);
                    return NetStatus.Code.PROTOCOL_DROP_ERROR;
                }

                // SYNC causes us to issue a RST and abort
                if (TcpFormat.IsSync(ref tcpHeader)) {
                    // send a reset
                    HandleTerminateSession(tcpSession,
                                           CreateResetSegment(tcpSession, false),
                                           TcpError.ProtocolViolation);
                    return NetStatus.Code.PROTOCOL_DROP_ERROR;
                }

                if (!IsSegmentAcceptable(tcpSession, ref tcpHeader, segmentSize)) {
                    // Just drop it
                    return NetStatus.Code.PROTOCOL_DROP_ERROR;
                }

                // Do ACK housekeeping
                if (TCPSeqLess(tcpSession.sessionTCB.SND.UNA, tcpHeader.ackSeq) &&
                    TCPSeqLEQ(tcpHeader.ackSeq, tcpSession.sessionTCB.SND.NXT))
                {
                    // This appears to be ACKing something we sent.
                    tcpSession.sessionTCB.SND.UNA = tcpHeader.ackSeq;

                    // remove the packet(s) from the retransmit queue
                    TCPFSM.HandleTCPAck(tcpSession, ref tcpHeader);
                }

                // Note the weirdness here; because we
                // prepacketize data, we don't want to compare against
                // SND.NXT
                if (TCPSeqGEQ(tcpSession.sessionTCB.SND.UNA, tcpSession.sessionTCB.SND.NextSeq)) {
                    // The remote side has now heard everything we said.
                    HandleTerminateSession(tcpSession, null, TcpError.Closed);
                }

                return NetStatus.Code.PROTOCOL_DROP_ERROR;
            }
        }

        //----------------------------------------------------------------------------
        // Create an ACK for data we have received to date
        internal static TcpSegment! CreateAckSegment(TcpSession! tcpSession)
        {
            byte[] ackBuffer = new byte[EthernetFormat.Size+IPFormat.Size+TcpFormat.Size];

            TcpFormat.WriteTcpSegment(
                ackBuffer, tcpSession.LocalPort, tcpSession.RemotePort,
                tcpSession.sessionTCB.RCV.NXT, tcpSession.sessionTCB.SND.NextSeq,
                TcpFormat.TCP_MSS, tcpSession.LocalAddress,
                tcpSession.RemoteAddress, 0,
                true, false, false, false, false);

            return new TcpSegment(ackBuffer, tcpSession,
                                  tcpSession.sessionTCB.SND.NextSeq, true);
        }

        // sends an ack
        internal static bool SendAck(TcpSession! tcpSession)
        {
            TcpSegment ackSeg = CreateAckSegment(tcpSession);

            // put it on this session outgoing queue
            return tcpSession.PutPacket(tcpSession.outQueue, ackSeg, false);
        }

        // Sends a FIN
        internal static bool SendFin(TcpSession! tcpSession, bool canBlock)
        {
            byte[] finBuffer = new byte[EthernetFormat.Size+IPFormat.Size+TcpFormat.Size];

            TcpFormat.WriteTcpSegment(
                finBuffer, tcpSession.LocalPort, tcpSession.RemotePort, tcpSession.sessionTCB.RCV.NXT,
                tcpSession.sessionTCB.SND.NextSeq, TcpFormat.TCP_MSS, tcpSession.LocalAddress, tcpSession.RemoteAddress, 0,
                true, false, false, true, false);

            // ok, we have a ready segment.
            TcpSegment syn = new TcpSegment(finBuffer, tcpSession, tcpSession.sessionTCB.SND.NextSeq, true);

            bool retVal = tcpSession.PutPacket(tcpSession.outQueue, syn, canBlock);

            // Only advance the segment counter if we successfully queued the
            // outbound packet
            if (retVal) {
                tcpSession.sessionTCB.SND.NextSeq++;
            }

            return retVal;
        }

        // sends a syn ack packet
        internal static bool SendSYNAck(TcpSession! tcpSession)
        {
            byte[] synackBuffer = new byte[EthernetFormat.Size+IPFormat.Size+TcpFormat.Size];

            TcpFormat.WriteTcpSegment(synackBuffer, tcpSession.LocalPort, tcpSession.RemotePort,
                                      tcpSession.sessionTCB.RCV.NXT,
                                      tcpSession.sessionTCB.SND.ISS,
                                      (ushort)tcpSession.sessionTCB.SND.WND,
                                      tcpSession.LocalAddress, tcpSession.RemoteAddress,
                                      0, true, true, false, false, false);

            // ok, we have a ready segment.
            // (SYN is regular segment)
            TcpSegment syn = new TcpSegment(synackBuffer, tcpSession, tcpSession.sessionTCB.SND.ISS, true);

            // put it on this session's outgoing queue
            return tcpSession.PutPacket(tcpSession.outQueue, syn, false);
        }

        // sends a syn packet, with MSS options
        internal static bool SendSYN(TcpSession! tcpSession, ushort MSS)
        {
            byte[] synBuffer = new byte[EthernetFormat.Size+IPFormat.Size+TcpFormat.Size+4];

            // setup the session
            tcpSession.sessionTCB.SND.ISS=(uint)DateTime.UtcNow.Ticks;
            tcpSession.sessionTCB.SND.UNA=tcpSession.sessionTCB.SND.ISS;
            tcpSession.sessionTCB.SND.NXT=tcpSession.sessionTCB.SND.ISS+1; // next packet sequence
            tcpSession.sessionTCB.SND.NextSeq = tcpSession.sessionTCB.SND.NXT;
            tcpSession.sessionTCB.SND.WND=TcpFormat.TCP_MSS;

            // we must first write the data...
            byte[] options=new byte[] {2, 4, ((byte)(MSS>>8)), (byte)MSS};
            Array.Copy(options, 0, synBuffer, EthernetFormat.Size+IPFormat.Size+TcpFormat.Size, 4);

            // now create the segment+checksum
            TcpFormat.WriteTcpSegment(
                synBuffer, tcpSession.LocalPort, tcpSession.RemotePort, 0,
                tcpSession.sessionTCB.SND.ISS, MSS, tcpSession.LocalAddress, tcpSession.RemoteAddress, 4,
                false, true, false, false, true);

            // ok, we have a ready segment.
            // (SYN is regular segment)
            TcpSegment syn = new TcpSegment(synBuffer, tcpSession, tcpSession.sessionTCB.SND.ISS, false);

            // put it on this session outgoing queue
            return tcpSession.PutPacket(tcpSession.outQueue, syn, false);
        }

        // send a TCP reset
        internal static bool SendReset(TcpSession! tcpSession, bool isAck)
        {
            // we won't retransmit it (it is like an Ack)
            TcpSegment syn = CreateResetSegment(tcpSession, true);

            // put it on this session outgoing queue
            return tcpSession.PutPacket(tcpSession.outQueue, syn, false);
        }

        // send a TCP reset
        internal static TcpSegment! CreateResetSegment(TcpSession! tcpSession, bool isAck)
        {
            byte[] rstBuffer = new byte[EthernetFormat.Size+IPFormat.Size+TcpFormat.Size];

            // NOTE: use SND.NXT as the sequence number here instead of nextSeq,
            // otherwise the sequence number may be far ahead (if there's lots of queued
            // data) and the receiving host may ignore it as outside its window.
            TcpFormat.WriteTcpSegment(
                rstBuffer, tcpSession.LocalPort, tcpSession.RemotePort, tcpSession.sessionTCB.RCV.NXT,
                tcpSession.sessionTCB.SND.NXT, TcpFormat.TCP_MSS, tcpSession.LocalAddress,
                tcpSession.RemoteAddress, 0, isAck, false, true, false, false);

            // we won't retransmit it
            TcpSegment syn = new TcpSegment(rstBuffer, tcpSession, tcpSession.sessionTCB.SND.NXT, true);
            return syn;
        }

        // Handle received TCP data
        // Returns the appropriate NetStatus.Code
        internal static NetStatus HandleTCPData(ref TcpFormat.TcpHeader tcpHeader,
                                                NetPacket! packet, TcpSession! tcpSession)
        {
            uint segmentSize = (uint)packet.Available;  // TCPModule already shrinked it for us

            // TBC: We assume the segment start with
            // the RCV.NXT !!! (otherwise we can buffer them)
            if (TCPSeqGreater(tcpHeader.seq, tcpSession.sessionTCB.RCV.NXT)) {
                // we missed one or few, send ack again
                TCPFSM.SendAck(tcpSession);
                return NetStatus.Code.PROTOCOL_DROP_ERROR;
            }

            if (!TcpFormat.IsAck(ref tcpHeader)) {
                return NetStatus.Code.PROTOCOL_DROP_ERROR;
            }

            // First, deal with the window advertised in this packet.
            tcpSession.sessionTCB.SND.WND = tcpHeader.window;
            tcpSession.HandleWindowUpdate();

            // ack is set and it is relevant (ack something we sent)
            if (TCPSeqLess(tcpSession.sessionTCB.SND.UNA, tcpHeader.ackSeq) &&
                TCPSeqLEQ(tcpHeader.ackSeq, tcpSession.sessionTCB.SND.NXT))
            {
                tcpSession.sessionTCB.SND.UNA = tcpHeader.ackSeq;

                // remove the packet(s) from the retransmit queue
                TCPFSM.HandleTCPAck(tcpSession, ref tcpHeader);
            }
            else if (tcpSession.sessionTCB.SND.UNA > tcpHeader.ackSeq) {
                // a duplicate ack
                return NetStatus.Code.PROTOCOL_DROP_ERROR;
            }
            else if (TCPSeqGreater(tcpHeader.ackSeq, tcpSession.sessionTCB.SND.NXT)) {
                // ack for future data..
                TCPFSM.SendAck(tcpSession);
                return NetStatus.Code.PROTOCOL_DROP_ERROR;
            }

            // check URG bit
            if (TcpFormat.IsUrg(ref tcpHeader)) {
                // TBC
            }

            // check PUSH bit
            if (TcpFormat.IsPush(ref tcpHeader)) {
                // TBC
            }

            // at last, process the segment data!!!
            // at the end send an ACK (TBC: change to accumulate acks)
            if (segmentSize > 0) {
                // put the data in the session's inQ
                if (tcpSession.PutPacket(tcpSession.inQueue, packet, false)) {
                    tcpSession.sessionTCB.RCV.NXT+=segmentSize; // we expect the next segment seq
                    tcpSession.sessionTCB.RCV.WND=TcpFormat.TCP_MSS; // TBC: change according to buffer

                    // send our ack if we ack data
                    SendAck(tcpSession);

                    // don't release the packet
                    return NetStatus.Code.PROTOCOL_PROCESSING;
                }
                else {
                    // packet was dropped...
                    // send our ack if we ack data
                    SendAck(tcpSession);
                    return NetStatus.Code.PROTOCOL_DROP_ERROR;
                }
            }
            else {
                // Payload was empty...
                return NetStatus.Code.PROTOCOL_DROP_ERROR;
            }
        }

        // handle a TCP received ack
        // the ack is already checked for validity (by the actual state)
        internal static void HandleTCPAck(TcpSession! tcpSession,
                                          ref TcpFormat.TcpHeader tcpHdr)
        {
            tcpSession.ACKThrough(tcpHdr.ackSeq);
        }

        // check if we can accept the segment
        internal static bool IsSegmentAcceptable(TcpSession! tcpSession,
                                                 ref TcpFormat.TcpHeader tcpHeader,
                                                 uint segmentSize)
        {
            bool accept=false;

            // first check sequence number
            if ((segmentSize == 0) && (tcpSession.sessionTCB.RCV.WND == 0)) {
                accept = (tcpHeader.seq == tcpSession.sessionTCB.RCV.NXT);
            }
            else if ((segmentSize == 0) && (tcpSession.sessionTCB.RCV.WND > 0)) {
                accept=(TCPSeqGEQ(tcpHeader.seq, tcpSession.sessionTCB.RCV.NXT) &&
                        TCPSeqLess(tcpHeader.seq, tcpSession.sessionTCB.RCV.NXT+tcpSession.sessionTCB.RCV.WND));
            }
            else if ((segmentSize > 0) && (tcpSession.sessionTCB.RCV.WND > 0)) {
                accept=(TCPSeqGEQ(tcpHeader.seq, tcpSession.sessionTCB.RCV.NXT) &&
                        TCPSeqLess(tcpHeader.seq, tcpSession.sessionTCB.RCV.NXT+tcpSession.sessionTCB.RCV.WND));

                accept = accept || (TCPSeqLEQ(tcpSession.sessionTCB.RCV.NXT, tcpHeader.seq+segmentSize-1) &&
                                    TCPSeqLess(tcpHeader.seq +segmentSize - 1,
                                               tcpSession.sessionTCB.RCV.NXT + tcpSession.sessionTCB.RCV.WND));
            }

            return accept;
        }

        private static void AttemptToEnterESTABLISHED(TcpSession! tcpSession,
                                                      ref TcpFormat.TcpHeader tcpHeader)
        {
            TcpSession passiveOwner = tcpSession.passiveSession;

            if (passiveOwner != null) {
                bool success = passiveOwner.AddAcceptedSession(tcpSession);

                if (!success) {
                    // Oops; while this connection was being established
                    // we ran out of room in the accept queue. Abort
                    // the connection!
                    HandleTerminateSession(tcpSession,
                                           CreateResetSegment(tcpSession, false),
                                           TcpError.ResourcesExhausted);
                    return;
                }
            }

            tcpSession.ChangeState(ESTABLISHED);
        }

        // this method terminated the given session
        // if nextSegment is not null than it is sent before final
        // removal.
        internal static void HandleTerminateSession(TcpSession! tcpSession,
                                                    TcpSegment nextSegment,
                                                    TcpError connectError)
        {
            tcpSession.StopPersistTimer();

            // 1. first clear the retransmit queue
            // 2. make the session not valid for users!
            // 3. release any user waiting for read/write on the session
            // 4. clear the out/in queues
            // 5. remove the session from TCP if it is no longer needed
            //    (when it considered to be closed)
            tcpSession.FlushRetransmissions();

            // some user may wait on the q to write/read data
            // we will release them by trigger the monitors.
            // they will fail since Valid is false and they are users.
            // only TCP can still use this session.
            lock (tcpSession.outQueue.SyncRoot) {
                // from now on, every user thread will
                // fail to read/write data
                tcpSession.ValidForRead = false;
                tcpSession.ValidForWrite = false;

                tcpSession.DrainQueue(tcpSession.outQueue);
                if (nextSegment != null)
                    tcpSession.outQueue.Add(nextSegment);

                // Don't forget these!
                Monitor.PulseAll(tcpSession.outQueue.SyncRoot);
                Core.Instance().SignalOutboundPackets();
            }

            lock (tcpSession.inQueue.SyncRoot) {
                // Pulse anyone waiting to read data so they can notice
                // they are unlikely to succeed, but don't clear the
                // queue; if there is data on it, we may want to drain
                // it, still.
                Monitor.PulseAll(tcpSession.inQueue.SyncRoot);
            }

            if (nextSegment == null) {
                IProtocol tcpProtocol = tcpSession.Protocol;
                Core.Instance().DeregisterSession(tcpProtocol, tcpSession);
            }

            // change the state to close
            tcpSession.ChangeState(TCPFSM.CLOSED);

            // Signal anyone who was waiting for this session to begin or end
            tcpSession.connectError = connectError;
            tcpSession.setupCompleteEvent.Set();

            // NOTE: If we are transmitting a last-gasp packet,
            // don't signal the session as closed just yet; wait for the packet
            // to actually get transmitted.
            if (nextSegment == null) {
                tcpSession.closedEvent.Set();
            }
        }
    }
}
