using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Threading;
using UDTSOCKET = System.Int32;

namespace UdtSharp
{
    public enum UDTOpt
    {
        UDT_MSS,             // the Maximum Transfer Unit
        UDT_SNDSYN,          // if sending is blocking
        UDT_RCVSYN,          // if receiving is blocking
        UDT_CC,              // custom congestion control algorithm
        UDT_FC,              // Flight flag size (window size)
        UDT_SNDBUF,          // maximum buffer in sending queue
        UDT_RCVBUF,          // UDT receiving buffer size
        UDT_LINGER,          // waiting for unsent data when closing
        UDP_SNDBUF,          // UDP sending buffer size
        UDP_RCVBUF,          // UDP receiving buffer size
        UDT_MAXMSG,          // maximum datagram message size
        UDT_MSGTTL,          // time-to-live of a datagram message
        UDT_RENDEZVOUS,      // rendezvous connection mode
        UDT_SNDTIMEO,        // send() timeout
        UDT_RCVTIMEO,        // recv() timeout
        UDT_REUSEADDR,       // reuse an existing port or create a new one
        UDT_MAXBW,           // maximum bandwidth (bytes per second) that the connection can use
        UDT_STATE,           // current socket state, see UDTSTATUS, read only
        UDT_EVENT,           // current avalable events associated with the socket
        UDT_SNDDATA,         // size of data in the sending buffer
        UDT_RCVDATA          // size of data available for recv
    };

    public enum UDTSTATUS
    {
        INIT = 1,
        OPENED,
        LISTENING,
        CONNECTING,
        CONNECTED,
        BROKEN,
        CLOSING,
        CLOSED,
        NONEXIST
    };

    enum EPOLLOpt
    {
        // this values are defined same as linux epoll.h
        // so that if system values are used by mistake, they should have the same effect
        UDT_EPOLL_IN = 0x1,
        UDT_EPOLL_OUT = 0x4,
        UDT_EPOLL_ERR = 0x8
    };

    public class PerfMon
    {
        // global measurements
        internal long msTimeStamp;                    // time since the UDT entity is started, in milliseconds
        internal long pktSentTotal;                   // total number of sent data packets, including retransmissions
        internal long pktRecvTotal;                   // total number of received packets
        internal int pktSndLossTotal;                 // total number of lost packets (sender side)
        internal int pktRcvLossTotal;                 // total number of lost packets (receiver side)
        internal int pktRetransTotal;                 // total number of retransmitted packets
        internal int pktSentACKTotal;                 // total number of sent ACK packets
        internal int pktRecvACKTotal;                 // total number of received ACK packets
        internal int pktSentNAKTotal;                 // total number of sent NAK packets
        internal int pktRecvNAKTotal;                 // total number of received NAK packets
        internal long usSndDurationTotal;             // total time duration when UDT is sending data (idle time exclusive)

        // local measurements
        internal long pktSent;                        // number of sent data packets, including retransmissions
        internal long pktRecv;                        // number of received packets
        internal int pktSndLoss;                      // number of lost packets (sender side)
        internal int pktRcvLoss;                      // number of lost packets (receiver side)
        internal int pktRetrans;                      // number of retransmitted packets
        internal int pktSentACK;                      // number of sent ACK packets
        internal int pktRecvACK;                      // number of received ACK packets
        internal int pktSentNAK;                      // number of sent NAK packets
        internal int pktRecvNAK;                      // number of received NAK packets
        internal double mbpsSendRate;                 // sending rate in Mb/s
        internal double mbpsRecvRate;                 // receiving rate in Mb/s
        internal long usSndDuration;                  // busy sending time (i.e., idle time exclusive)

        // instant measurements
        internal double usPktSndPeriod;               // packet sending period, in microseconds
        internal int pktFlowWindow;                   // flow window size, in number of packets
        internal int pktCongestionWindow;             // congestion window size, in number of packets
        internal int pktFlightSize;                   // number of packets on flight
        internal double msRTT;                        // RTT, in milliseconds
        internal double mbpsBandwidth;                // estimated bandwidth, in Mb/s
        internal int byteAvailSndBuf;                 // available UDT sender buffer size
        internal int byteAvailRcvBuf;                 // available UDT receiver buffer size
    };

    public class Unit
    {
        public Packet m_Packet = new Packet();       // packet
        public int m_iFlag;            // 0: free, 1: occupied, 2: msg read but not freed (out-of-order), 3: msg dropped
    };

    public class UDT
    {
        public const UDTSOCKET INVALID_SOCK = -1;
        public const int ERROR = -1;
        const int m_iVersion = 4;

        public static UdtUnited s_UDTUnited = new UdtUnited();               // UDT global management base

        // Identification
        public UDTSOCKET m_SocketID;                        // UDT socket number
        public SocketType m_iSockType;                     // Type of the UDT connection (SOCK_STREAM or SOCK_DGRAM)
        public UDTSOCKET m_PeerID;             // peer id, for multiplexer

        // Packet sizes
        int m_iPktSize;                              // Maximum/regular packet size, in bytes
        public int m_iPayloadSize;                          // Maximum/regular payload size, in bytes

        // Options
        public int m_iMSS;                                  // Maximum Segment Size, in bytes
        bool m_bSynSending;                          // Sending syncronization mode
        public bool m_bSynRecving;                          // Receiving syncronization mode
        public int m_iFlightFlagSize;                       // Maximum number of packets in flight from the peer side
        int m_iSndBufSize;                           // Maximum UDT sender buffer size
        int m_iRcvBufSize;                           // Maximum UDT receiver buffer size
        LingerOption m_Linger;                             // Linger information on close
        public int m_iUDPSndBufSize;                        // UDP sending buffer size
        public int m_iUDPRcvBufSize;                        // UDP receiving buffer size
        public AddressFamily m_iIPversion;                            // IP version
        public bool m_bRendezvous;                          // Rendezvous connection mode
        int m_iSndTimeOut;                           // sending timeout in milliseconds
        int m_iRcvTimeOut;                           // receiving timeout in milliseconds
        public bool m_bReuseAddr;              // reuse an exiting port or not, for UDP multiplexer
        long m_llMaxBW;              // maximum data transfer rate (threshold)

        // congestion control
        CCVirtualFactory m_pCCFactory;             // Factory class to create a specific CC instance
        CC m_pCC;                                  // congestion control class
        public HashSet<InfoBlock> m_pCache = new HashSet<InfoBlock>();       // network information cache

        // Status
        volatile bool m_bListening;                  // If the UDT entit is listening to connection
        volatile bool m_bConnecting;            // The short phase when connect() is called but not yet completed
        public volatile bool m_bConnected;                  // Whether the connection is on or off
        public volatile bool m_bClosing;                    // If the UDT entity is closing
        volatile bool m_bShutdown;                   // If the peer side has shutdown the connection
        public volatile bool m_bBroken;                     // If the connection has been broken
        volatile bool m_bPeerHealth;                 // If the peer status is normal
        bool m_bOpened;                              // If the UDT entity has been opened
        public int m_iBrokenCounter;           // a counter (number of GC checks) to let the GC tag this socket as disconnected

        int m_iEXPCount;                             // Expiration counter
        int m_iBandwidth;                            // Estimated bandwidth, number of packets per second
        int m_iRTT;                                  // RTT, in microseconds
        int m_iRTTVar;                               // RTT variance
        int m_iDeliveryRate;                // Packet arrival rate at the receiver side

        public ulong m_ullLingerExpiration;     // Linger expiration time (for GC to close a socket with data in sending buffer)

        public Handshake m_ConnReq = new Handshake();           // connection request
        public Handshake m_ConnRes = new Handshake();           // connection response
        public long m_llLastReqTime;            // last time when a connection request is sent

        // Sending related data
        public SndBuffer m_pSndBuffer;                    // Sender buffer
        SndLossList m_pSndLossList;                // Sender loss list
        PktTimeWindow m_pSndTimeWindow;            // Packet sending time window

        /*volatile*/
        ulong m_ullInterval;             // Inter-packet time, in CPU clock cycles
        ulong m_ullTimeDiff;                      // aggregate difference in inter-packet time

        volatile int m_iFlowWindowSize;              // Flow control window size
        /*volatile*/
        double m_dCongestionWindow;         // congestion window size

        volatile int m_iSndLastAck;              // Last ACK received
        volatile int m_iSndLastDataAck;          // The real last ACK that updates the sender buffer and loss list
        volatile int m_iSndCurrSeqNo;            // The largest sequence number that has been sent
        int m_iLastDecSeq;                       // Sequence number sent last decrease occurs
        int m_iSndLastAck2;                      // Last ACK2 sent back
        ulong m_ullSndLastAck2Time;               // The time when last ACK2 was sent back

        public int m_iISN;                              // Initial Sequence Number

        // Receiving related data
        public RcvBuffer m_pRcvBuffer;                    // Receiver buffer
        RcvLossList m_pRcvLossList;                // Receiver loss list
        ACKWindow m_pACKWindow;                    // ACK history window
        PktTimeWindow m_pRcvTimeWindow;            // Packet arrival time window

        int m_iRcvLastAck;                       // Last sent ACK
        ulong m_ullLastAckTime;                   // Timestamp of last ACK
        int m_iRcvLastAckAck;                    // Last sent ACK that has been acknowledged
        int m_iAckSeqNo;                         // Last ACK sequence number
        int m_iRcvCurrSeqNo;                     // Largest received sequence number

        ulong m_ullLastWarningTime;               // Last time that a warning message is sent

        int m_iPeerISN;                          // Initial Sequence Number of the peer side

        // synchronization: mutexes and conditions
        readonly object m_ConnectionLock = new object();            // used to synchronize connection operation

        readonly EventWaitHandle m_SendBlockCond = new EventWaitHandle(false, EventResetMode.AutoReset);              // used to block "send" call
        readonly object m_SendBlockLock = new object();             // lock associated to m_SendBlockCond

        readonly object m_AckLock = new object();                   // used to protected sender's loss list when processing ACK

        readonly EventWaitHandle m_RecvDataCond = new EventWaitHandle(false, EventResetMode.AutoReset);               // used to block "recv" when there is no data
        readonly object m_RecvDataLock = new object();              // lock associated to m_RecvDataCond

        readonly object m_SendLock = new object();                  // used to synchronize "send" call
        readonly object m_RecvLock = new object();                  // used to synchronize "recv" call

        // Trace
        ulong m_StartTime;                        // timestamp when the UDT entity is started
        long m_llSentTotal;                       // total number of sent data packets, including retransmissions
        long m_llRecvTotal;                       // total number of received packets
        int m_iSndLossTotal;                         // total number of lost packets (sender side)
        int m_iRcvLossTotal;                         // total number of lost packets (receiver side)
        int m_iRetransTotal;                         // total number of retransmitted packets
        int m_iSentACKTotal;                         // total number of sent ACK packets
        int m_iRecvACKTotal;                         // total number of received ACK packets
        int m_iSentNAKTotal;                         // total number of sent NAK packets
        int m_iRecvNAKTotal;                         // total number of received NAK packets
        long m_llSndDurationTotal;       // total real time for sending

        ulong m_LastSampleTime;                   // last performance sample time
        long m_llTraceSent;                       // number of pakctes sent in the last trace interval
        long m_llTraceRecv;                       // number of pakctes received in the last trace interval
        int m_iTraceSndLoss;                         // number of lost packets in the last trace interval (sender side)
        int m_iTraceRcvLoss;                         // number of lost packets in the last trace interval (receiver side)
        int m_iTraceRetrans;                         // number of retransmitted packets in the last trace interval
        int m_iSentACK;                              // number of ACKs sent in the last trace interval
        int m_iRecvACK;                              // number of ACKs received in the last trace interval
        int m_iSentNAK;                              // number of NAKs sent in the last trace interval
        int m_iRecvNAK;                              // number of NAKs received in the last trace interval
        long m_llSndDuration;            // real time for sending
        long m_llSndDurationCounter;     // timers to record the sending duration

        // Timers
        ulong m_ullCPUFrequency;                  // CPU clock frequency, used for Timer, ticks per microsecond

        public const int m_iSYNInterval = 10000;             // Periodical Rate Control Interval, 10000 microsecond
        const int m_iSelfClockInterval = 64;       // ACK interval for self-clocking

        ulong m_ullNextACKTime;          // Next ACK time, in CPU clock cycles, same below
        ulong m_ullNextNAKTime;          // Next NAK time

        /*volatile*/
        ulong m_ullSYNInt;      // SYN interval
        /*volatile*/
        ulong m_ullACKInt;      // ACK interval
        /*volatile*/
        ulong m_ullNAKInt;      // NAK interval
        /*volatile*/
        ulong m_ullLastRspTime;     // time stamp of last response from the peer

        ulong m_ullMinNakInt;            // NAK timeout lower bound; too small value can cause unnecessary retransmission
        ulong m_ullMinExpInt;            // timeout lower bound threshold: too small timeout can cause problem

        int m_iPktCount;                // packet counter for ACK
        int m_iLightACKCount;           // light ACK counter

        ulong m_ullTargetTime;           // scheduled time of next packet sending

        // for UDP multiplexer
        public SndQueue m_pSndQueue;          // packet sending queue
        public RcvQueue m_pRcvQueue;         // packet receiving queue
        public IPEndPoint m_pPeerAddr;          // peer address
        public uint[] m_piSelfIP = new uint[4];         // local UDP IP address
        public SNode m_pSNode;               // node information for UDT list used in snd queue
        public RNode m_pRNode;               // node information for UDT list used in rcv queue

        public UDT()
        {
            // Default UDT configurations
            m_iMSS = 1500;
            m_bSynSending = true;
            m_bSynRecving = true;
            m_iFlightFlagSize = 204800;
            m_iSndBufSize = 65536;
            m_iRcvBufSize = 65536; //Rcv buffer MUST NOT be bigger than Flight Flag size
            m_Linger = new LingerOption(true, 180);
            m_iUDPSndBufSize = 524288;
            m_iUDPRcvBufSize = m_iRcvBufSize * m_iMSS;
            m_iSockType = SocketType.Stream;
            m_iIPversion = AddressFamily.InterNetwork;
            m_bRendezvous = false;
            m_iSndTimeOut = -1;
            m_iRcvTimeOut = -1;
            m_bReuseAddr = true;
            m_llMaxBW = -1;

            m_pCCFactory = new CCFactory<UDTCC>();

            // Initial status
            m_bOpened = false;
            m_bListening = false;
            m_bConnecting = false;
            m_bConnected = false;
            m_bClosing = false;
            m_bShutdown = false;
            m_bBroken = false;
            m_bPeerHealth = true;
            m_ullLingerExpiration = 0;
        }

        public UDT(UDT ancestor)
        {
            // Default UDT configurations
            m_iMSS = ancestor.m_iMSS;
            m_bSynSending = ancestor.m_bSynSending;
            m_bSynRecving = ancestor.m_bSynRecving;
            m_iFlightFlagSize = ancestor.m_iFlightFlagSize;
            m_iSndBufSize = ancestor.m_iSndBufSize;
            m_iRcvBufSize = ancestor.m_iRcvBufSize;
            m_Linger = ancestor.m_Linger;
            m_iUDPSndBufSize = ancestor.m_iUDPSndBufSize;
            m_iUDPRcvBufSize = ancestor.m_iUDPRcvBufSize;
            m_iSockType = ancestor.m_iSockType;
            m_iIPversion = ancestor.m_iIPversion;
            m_bRendezvous = ancestor.m_bRendezvous;
            m_iSndTimeOut = ancestor.m_iSndTimeOut;
            m_iRcvTimeOut = ancestor.m_iRcvTimeOut;
            m_bReuseAddr = true;    // this must be true, because all accepted sockets shared the same port with the listener
            m_llMaxBW = ancestor.m_llMaxBW;

            m_pCCFactory = ancestor.m_pCCFactory.clone();
            m_pCC = null;
            m_pCache = ancestor.m_pCache;

            // Initial status
            m_bOpened = false;
            m_bListening = false;
            m_bConnecting = false;
            m_bConnected = false;
            m_bClosing = false;
            m_bShutdown = false;
            m_bBroken = false;
            m_bPeerHealth = true;
            m_ullLingerExpiration = 0;
        }

        ~UDT()
        {
            // release mutex/condtion variables
            destroySynch();

        }

        public unsafe void setOpt(UDTOpt optName, int optval, UDTSOCKET socket)
        {
            setOpt(optName, &optval, socket);
        }

        public unsafe void setOpt(UDTOpt optName, void* optval, UDTSOCKET socket)
        {
            if (m_bBroken || m_bClosing)
                throw new UdtException(2, 1, 0);

            lock (m_ConnectionLock) lock(m_SendLock) lock(m_RecvLock)
            {
                setOpt_unsafe(optName, optval, socket);
            }
        }

        unsafe void setOpt_unsafe(UDTOpt optName, void* optval, UDTSOCKET socket)
        {
            switch (optName)
            {
                case UDTOpt.UDT_MSS:
                    if (m_bOpened)
                        throw new UdtException(5, 1, 0);

                    if (*(int*)optval < (int)(28 + Handshake.m_iContentSize))
                        throw new UdtException(5, 3, 0);

                    m_iMSS = *(int*)optval;

                    // Packet size cannot be greater than UDP buffer size
                    if (m_iMSS > m_iUDPSndBufSize)
                        m_iMSS = m_iUDPSndBufSize;
                    if (m_iMSS > m_iUDPRcvBufSize)
                        m_iMSS = m_iUDPRcvBufSize;

                    break;

                case UDTOpt.UDT_SNDSYN:
                    m_bSynSending = *(bool*)optval;
                    break;

                case UDTOpt.UDT_RCVSYN:
                    m_bSynRecving = *(bool*)optval;
                    break;

                case UDTOpt.UDT_CC:
                    if (m_bConnecting || m_bConnected)
                        throw new UdtException(5, 1, 0);
                    //m_pCCFactory = (&(CCCVirtualFactory*)optval).clone();

                    break;

                case UDTOpt.UDT_FC:
                    if (m_bConnecting || m_bConnected)
                        throw new UdtException(5, 2, 0);

                    if (*(int*)optval < 1)
                        throw new UdtException(5, 3);

                    // Mimimum recv flight flag size is 32 packets
                    if (*(int*)optval > 32)
                        m_iFlightFlagSize = *(int*)optval;
                    else
                        m_iFlightFlagSize = 32;

                    break;

                case UDTOpt.UDT_SNDBUF:
                    if (m_bOpened)
                        throw new UdtException(5, 1, 0);

                    if (*(int*)optval <= 0)
                        throw new UdtException(5, 3, 0);

                    m_iSndBufSize = *(int*)optval / (m_iMSS - 28);

                    break;

                case UDTOpt.UDT_RCVBUF:
                    if (m_bOpened)
                        throw new UdtException(5, 1, 0);

                    if (*(int*)optval <= 0)
                        throw new UdtException(5, 3, 0);

                    // Mimimum recv buffer size is 32 packets
                    if (*(int*)optval > (m_iMSS - 28) * 32)
                        m_iRcvBufSize = *(int*)optval / (m_iMSS - 28);
                    else
                        m_iRcvBufSize = 32;

                    // recv buffer MUST not be greater than FC size
                    if (m_iRcvBufSize > m_iFlightFlagSize)
                        m_iRcvBufSize = m_iFlightFlagSize;

                    break;

                case UDTOpt.UDT_LINGER:
                    m_Linger = ConvertLingerOption.FromVoidPointer(optval);
                    break;

                case UDTOpt.UDP_SNDBUF:
                    if (m_bOpened)
                        throw new UdtException(5, 1, 0);

                    m_iUDPSndBufSize = *(int*)optval;

                    if (m_iUDPSndBufSize < m_iMSS)
                        m_iUDPSndBufSize = m_iMSS;

                    break;

                case UDTOpt.UDP_RCVBUF:
                    if (m_bOpened)
                        throw new UdtException(5, 1, 0);

                    m_iUDPRcvBufSize = *(int*)optval;

                    if (m_iUDPRcvBufSize < m_iMSS)
                        m_iUDPRcvBufSize = m_iMSS;

                    break;

                case UDTOpt.UDT_RENDEZVOUS:
                    if (m_bConnecting || m_bConnected)
                        throw new UdtException(5, 1, 0);
                    m_bRendezvous = *(bool*)optval;
                    break;

                case UDTOpt.UDT_SNDTIMEO:
                    m_iSndTimeOut = *(int*)optval;
                    break;

                case UDTOpt.UDT_RCVTIMEO:
                    m_iRcvTimeOut = *(int*)optval;
                    break;

                case UDTOpt.UDT_REUSEADDR:
                    if (m_bOpened)
                        throw new UdtException(5, 1, 0);
                    m_bReuseAddr = *(bool*)optval;
                    break;

                case UDTOpt.UDT_MAXBW:
                    m_llMaxBW = *(long*)optval;
                    break;

                default:
                    throw new UdtException(5, 0, 0);
            }
        }

        public unsafe void getOpt(UDTOpt optName, void* optval, ref int optlen)
        {
            lock (m_ConnectionLock)
            {
                getOpt_unsafe(optName, optval, ref optlen);
            }
        }

        unsafe void getOpt_unsafe(UDTOpt optName, void* optval, ref int optlen)
        {
            switch (optName)
            {
                case UDTOpt.UDT_MSS:
                    *(int*)optval = m_iMSS;
                    optlen = sizeof(int);
                    break;

                case UDTOpt.UDT_SNDSYN:
                    *(bool*)optval = m_bSynSending;
                    optlen = sizeof(bool);
                    break;

                case UDTOpt.UDT_RCVSYN:
                    *(bool*)optval = m_bSynRecving;
                    optlen = sizeof(bool);
                    break;

                case UDTOpt.UDT_CC:
                    if (!m_bOpened)
                        throw new UdtException(5, 5, 0);
                    //*(CC**)optval = m_pCC;
                    //optlen = sizeof(CC*);

                    break;

                case UDTOpt.UDT_FC:
                    *(int*)optval = m_iFlightFlagSize;
                    optlen = sizeof(int);
                    break;

                case UDTOpt.UDT_SNDBUF:
                    *(int*)optval = m_iSndBufSize * (m_iMSS - 28);
                    optlen = sizeof(int);
                    break;

                case UDTOpt.UDT_RCVBUF:
                    *(int*)optval = m_iRcvBufSize * (m_iMSS - 28);
                    optlen = sizeof(int);
                    break;

                case UDTOpt.UDT_LINGER:
                    if (optlen < 5) //?? (int)(sizeof(LingerOption)))
                        throw new UdtException(5, 3, 0);
                    ConvertLingerOption.ToVoidPointer(m_Linger, optval);
                    optlen = 5; //??
                    break;

                case UDTOpt.UDP_SNDBUF:
                    *(int*)optval = m_iUDPSndBufSize;
                    optlen = sizeof(int);
                    break;

                case UDTOpt.UDP_RCVBUF:
                    *(int*)optval = m_iUDPRcvBufSize;
                    optlen = sizeof(int);
                    break;

                case UDTOpt.UDT_RENDEZVOUS:
                    *(bool*)optval = m_bRendezvous;
                    optlen = sizeof(bool);
                    break;

                case UDTOpt.UDT_SNDTIMEO:
                    *(int*)optval = m_iSndTimeOut;
                    optlen = sizeof(int);
                    break;

                case UDTOpt.UDT_RCVTIMEO:
                    *(int*)optval = m_iRcvTimeOut;
                    optlen = sizeof(int);
                    break;

                case UDTOpt.UDT_REUSEADDR:
                    *(bool*)optval = m_bReuseAddr;
                    optlen = sizeof(bool);
                    break;

                case UDTOpt.UDT_MAXBW:
                    *(long*)optval = m_llMaxBW;
                    optlen = sizeof(long);
                    break;

                case UDTOpt.UDT_STATE:
                    *(int*)optval = (int)s_UDTUnited.getStatus(m_SocketID);
                    optlen = sizeof(int);
                    break;

                case UDTOpt.UDT_EVENT:
                    {
                        int udtevent = 0;
                        if (m_bBroken)
                            udtevent |= (int)EPOLLOpt.UDT_EPOLL_ERR;
                        else
                        {
                            if (m_pRcvBuffer != null && (m_pRcvBuffer.getRcvDataSize() > 0))
                                udtevent |= (int)EPOLLOpt.UDT_EPOLL_IN;
                            if (m_pSndBuffer != null && (m_iSndBufSize > m_pSndBuffer.getCurrBufSize()))
                                udtevent |= (int)EPOLLOpt.UDT_EPOLL_OUT;
                        }
                        *(int*)optval = udtevent;
                        optlen = sizeof(int);
                    }
                    break;

                case UDTOpt.UDT_SNDDATA:
                    if (m_pSndBuffer != null)
                        *(int*)optval = m_pSndBuffer.getCurrBufSize();
                    else
                        *(int*)optval = 0;
                    optlen = sizeof(int);
                    break;

                case UDTOpt.UDT_RCVDATA:
                    if (m_pRcvBuffer != null)
                        *(int*)optval = m_pRcvBuffer.getRcvDataSize();
                    else
                        *(int*)optval = 0;
                    optlen = sizeof(int);
                    break;

                default:
                    throw new UdtException(5, 0, 0);
            }
        }

        public unsafe void open()
        {
            lock (m_ConnectionLock)
            {
                open_unsafe();
            }
        }

        unsafe void open_unsafe()
        {
            // Initial sequence number, loss, acknowledgement, etc.
            m_iPktSize = m_iMSS - 28;
            m_iPayloadSize = m_iPktSize - Packet.m_iPktHdrSize;

            m_iEXPCount = 1;
            m_iBandwidth = 1;
            m_iDeliveryRate = 16;
            m_iAckSeqNo = 0;
            m_ullLastAckTime = 0;

            // trace information
            
            m_StartTime = Timer.getTime();
            m_llSentTotal = m_llRecvTotal = m_iSndLossTotal = m_iRcvLossTotal = m_iRetransTotal = m_iSentACKTotal = m_iRecvACKTotal = m_iSentNAKTotal = m_iRecvNAKTotal = 0;
            m_LastSampleTime = Timer.getTime();
            m_llTraceSent = m_llTraceRecv = m_iTraceSndLoss = m_iTraceRcvLoss = m_iTraceRetrans = m_iSentACK = m_iRecvACK = m_iSentNAK = m_iRecvNAK = 0;
            m_llSndDuration = m_llSndDurationTotal = 0;

            // structures for queue
            if (null == m_pSNode)
                m_pSNode = new SNode();
            m_pSNode.m_pUDT = this;
            m_pSNode.m_llTimeStamp = 1;
            m_pSNode.m_iHeapLoc = -1;

            if (null == m_pRNode)
                m_pRNode = new RNode();
            m_pRNode.m_pUDT = this;
            m_pRNode.m_llTimeStamp = 1;
            //m_pRNode.m_pPrev = m_pRNode.m_pNext = null;
            m_pRNode.m_bOnList = false;

            m_iRTT = 10 * m_iSYNInterval;
            m_iRTTVar = m_iRTT >> 1;

            m_ullCPUFrequency = Timer.getCPUFrequency();

            // set up the timers
            m_ullSYNInt = m_iSYNInterval * m_ullCPUFrequency;

            // set minimum NAK and EXP timeout to 100ms
            m_ullMinNakInt = 300000 * m_ullCPUFrequency;
            m_ullMinExpInt = 300000 * m_ullCPUFrequency;

            m_ullACKInt = m_ullSYNInt;
            m_ullNAKInt = m_ullMinNakInt;


            ulong currtime = Timer.rdtsc();
            m_ullLastRspTime = currtime;
            m_ullNextACKTime = currtime + m_ullSYNInt;
            m_ullNextNAKTime = currtime + m_ullNAKInt;

            m_iPktCount = 0;
            m_iLightACKCount = 1;

            m_ullTargetTime = 0;
            m_ullTimeDiff = 0;

            // Now UDT is opened.
            m_bOpened = true;
        }

        public void listen()
        {
            lock (m_ConnectionLock)
            {
                listen_unsafe();
            }
        }

        void listen_unsafe()
        {
            if (!m_bOpened)
                throw new UdtException(5, 0, 0);

            if (m_bConnecting || m_bConnected)
                throw new UdtException(5, 2, 0);

            // listen can be called more than once
            if (m_bListening)
                return;

            // if there is already another socket listening on the same port
            if (m_pRcvQueue.setListener(this) < 0)
                throw new UdtException(5, 11, 0);

            m_bListening = true;
        }

        public unsafe void connect(IPEndPoint serv_addr)
        {
            lock (m_ConnectionLock)
            {
                connect_unsafe(serv_addr);
            }
        }

        unsafe void connect_unsafe(IPEndPoint serv_addr)
        {
            if (!m_bOpened)
                throw new UdtException(5, 0, 0);

            if (m_bListening)
                throw new UdtException(5, 2, 0);

            if (m_bConnecting || m_bConnected)
                throw new UdtException(5, 2, 0);

            m_bConnecting = true;

            // record peer/server address
            m_pPeerAddr = serv_addr;

            // register this socket in the rendezvous queue
            // RendezevousQueue is used to temporarily store incoming handshake, non-rendezvous connections also require this function
            ulong ttl = 3000000;
            if (m_bRendezvous)
                ttl *= 10;
            ttl += Timer.getTime();
            m_pRcvQueue.registerConnector(m_SocketID, this, m_iIPversion, serv_addr, ttl);

            // This is my current configurations
            m_ConnReq.m_iVersion = m_iVersion;
            m_ConnReq.m_iType = m_iSockType;
            m_ConnReq.m_iMSS = m_iMSS;
            m_ConnReq.m_iFlightFlagSize = (m_iRcvBufSize < m_iFlightFlagSize) ? m_iRcvBufSize : m_iFlightFlagSize;
            m_ConnReq.m_iReqType = (!m_bRendezvous) ? 1 : 0;
            m_ConnReq.m_iID = m_SocketID;
            ConvertIPAddress.ToUintArray(serv_addr.Address, ref m_ConnReq.m_piPeerIP);

            // Random Initial Sequence Number
            Random rnd = new Random();
            m_iISN = m_ConnReq.m_iISN = rnd.Next(1, SequenceNumber.m_iMaxSeqNo);

            m_iLastDecSeq = m_iISN - 1;
            m_iSndLastAck = m_iISN;
            m_iSndLastDataAck = m_iISN;
            m_iSndCurrSeqNo = m_iISN - 1;
            m_iSndLastAck2 = m_iISN;
            m_ullSndLastAck2Time = Timer.getTime();

            // Inform the server my configurations.
            Packet request = new Packet();
            request.pack(m_ConnReq);
            // ID = 0, connection request
            request.SetId(0);

            m_pSndQueue.sendto(serv_addr, request);
            m_llLastReqTime = (long)Timer.getTime();

            // asynchronous connect, return immediately
            if (!m_bSynRecving)
            {
                return;
            }

            // Wait for the negotiated configurations from the peer side.
            Packet response = new Packet();
            byte[] resdata = new byte[m_iPayloadSize];
            response.pack(0, resdata);

            UdtException e = new UdtException(0, 0);

            while (!m_bClosing)
            {
                // avoid sending too many requests, at most 1 request per 250ms
                if ((long)Timer.getTime() - m_llLastReqTime > 250000)
                {
                    request.pack(m_ConnReq);
                    if (m_bRendezvous)
                        request.SetId(m_ConnRes.m_iID);
                    m_pSndQueue.sendto(serv_addr, request);
                    m_llLastReqTime = (long)Timer.getTime();
                }

                response.setLength(m_iPayloadSize);
                if (m_pRcvQueue.recvfrom(m_SocketID, response) > 0)
                {
                    if (connect(response) <= 0)
                        break;

                    // new request/response should be sent out immediately on receving a response
                    m_llLastReqTime = 0;
                }

                if (Timer.getTime() > ttl)
                {
                    // timeout
                    e = new UdtException(1, 1, 0);
                    break;
                }
            }

            if (e.getErrorCode() == 0)
            {
                if (m_bClosing)                                                 // if the socket is closed before connection...
                    e = new UdtException(1);
                else if (1002 == m_ConnRes.m_iReqType)                          // connection request rejected
                    e = new UdtException(1, 2, 0);
                else if ((!m_bRendezvous) && (m_iISN != m_ConnRes.m_iISN))      // secuity check
                    e = new UdtException(1, 4, 0);
            }

            if (e.getErrorCode() != 0)
                throw e;
        }

        public int connect(Packet response)
        {
           // this is the 2nd half of a connection request. If the connection is setup successfully this returns 0.
           // returning -1 means there is an error.
           // returning 1 or 2 means the connection is in process and needs more handshake

           if (!m_bConnecting)
              return -1;

           if (m_bRendezvous && ((0 == response.getFlag()) || (1 == response.getType())) && (0 != m_ConnRes.m_iType))
           {
              //a data packet or a keep-alive packet comes, which means the peer side is already connected
              // in this situation, the previously recorded response will be used
              goto POST_CONNECT;
           }

           if ((1 != response.getFlag()) || (0 != response.getType()))
              return -1;

           m_ConnRes.deserialize(response.GetDataBytes(), response.getLength());

           if (m_bRendezvous)
           {
              // regular connect should NOT communicate with rendezvous connect
              // rendezvous connect require 3-way handshake
              if (1 == m_ConnRes.m_iReqType)
                 return -1;

              if ((0 == m_ConnReq.m_iReqType) || (0 == m_ConnRes.m_iReqType))
              {
                 m_ConnReq.m_iReqType = -1;
                 // the request time must be updated so that the next handshake can be sent out immediately.
                 m_llLastReqTime = 0;
                 return 1;
              }
           }
           else
           {
              // set cookie
              if (1 == m_ConnRes.m_iReqType)
              {
                 m_ConnReq.m_iReqType = -1;
                 m_ConnReq.m_iCookie = m_ConnRes.m_iCookie;
                 m_llLastReqTime = 0;
                 return 1;
              }
           }

            POST_CONNECT:

            // Remove from rendezvous queue
            m_pRcvQueue.removeConnector(m_SocketID);

            // Re-configure according to the negotiated values.
            m_iMSS = m_ConnRes.m_iMSS;
            m_iFlowWindowSize = m_ConnRes.m_iFlightFlagSize;
            m_iPktSize = m_iMSS - 28;
            m_iPayloadSize = m_iPktSize - Packet.m_iPktHdrSize;
            m_iPeerISN = m_ConnRes.m_iISN;
            m_iRcvLastAck = m_ConnRes.m_iISN;
            m_iRcvLastAckAck = m_ConnRes.m_iISN;
            m_iRcvCurrSeqNo = m_ConnRes.m_iISN - 1;
            m_PeerID = m_ConnRes.m_iID;
            Array.Copy(m_ConnRes.m_piPeerIP, m_piSelfIP, 4);

            // Prepare all data structures
            try
            {
                m_pSndBuffer = new SndBuffer(32, m_iPayloadSize);
                m_pRcvBuffer = new RcvBuffer(m_iRcvBufSize);
                // after introducing lite ACK, the sndlosslist may not be cleared in time, so it requires twice space.
                m_pSndLossList = new SndLossList(m_iFlowWindowSize* 2);
                m_pRcvLossList = new RcvLossList(m_iFlightFlagSize);
                m_pACKWindow = new ACKWindow(1024);
                m_pRcvTimeWindow = new PktTimeWindow(16, 64);
                m_pSndTimeWindow = new PktTimeWindow();
            }
            catch (Exception e)
            {
                throw new UdtException(3, 2, 0);
            }

            InfoBlock ib = new InfoBlock(m_pPeerAddr.Address);
            if (m_pCache.TryGetValue(ib, out ib))
            {
                m_iRTT = ib.m_iRTT;
                m_iBandwidth = ib.m_iBandwidth;
            }

            m_pCC = m_pCCFactory.create();
            m_pCC.m_UDT = m_SocketID;
            m_pCC.setMSS(m_iMSS);
            m_pCC.setMaxCWndSize(m_iFlowWindowSize);
            m_pCC.setSndCurrSeqNo(m_iSndCurrSeqNo);
            m_pCC.setRcvRate(m_iDeliveryRate);
            m_pCC.setRTT(m_iRTT);
            m_pCC.setBandwidth(m_iBandwidth);
            m_pCC.init();

            m_ullInterval = (ulong) (m_pCC.m_dPktSndPeriod* m_ullCPUFrequency);
            m_dCongestionWindow = m_pCC.m_dCWndSize;

            // And, I am connected too.
            m_bConnecting = false;
            m_bConnected = true;

           // register this socket for receiving data packets
           m_pRNode.m_bOnList = true;
           m_pRcvQueue.setNewEntry(this);

            // acknowledge the management module.
            s_UDTUnited.connect_complete(m_SocketID);

           // acknowledde any waiting epolls to write
           //s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, EPOLLOpt.UDT_EPOLL_OUT, true);

           return 0;
        }

        public unsafe void connect(IPEndPoint peer, Handshake hs)
        {
            lock (m_ConnectionLock)
            {
                connect_unsafe(peer, hs);
            }
        }

        unsafe void connect_unsafe(IPEndPoint peer, Handshake hs)
        {
            // Uses the smaller MSS between the peers
            if (hs.m_iMSS > m_iMSS)
                hs.m_iMSS = m_iMSS;
            else
                m_iMSS = hs.m_iMSS;

            // exchange info for maximum flow window size
            m_iFlowWindowSize = hs.m_iFlightFlagSize;
            hs.m_iFlightFlagSize = (m_iRcvBufSize < m_iFlightFlagSize) ? m_iRcvBufSize : m_iFlightFlagSize;

            m_iPeerISN = hs.m_iISN;

            m_iRcvLastAck = hs.m_iISN;
            m_iRcvLastAckAck = hs.m_iISN;
            m_iRcvCurrSeqNo = hs.m_iISN - 1;

            m_PeerID = hs.m_iID;
            hs.m_iID = m_SocketID;

            // use peer's ISN and send it back for security check
            m_iISN = hs.m_iISN;

            m_iLastDecSeq = m_iISN - 1;
            m_iSndLastAck = m_iISN;
            m_iSndLastDataAck = m_iISN;
            m_iSndCurrSeqNo = m_iISN - 1;
            m_iSndLastAck2 = m_iISN;
            m_ullSndLastAck2Time = Timer.getTime();

            // this is a reponse handshake
            hs.m_iReqType = -1;

            // get local IP address and send the peer its IP address (because UDP cannot get local IP address)
            Array.Copy(hs.m_piPeerIP, m_piSelfIP, 4);
            ConvertIPAddress.ToUintArray(peer.Address, ref hs.m_piPeerIP);

            m_iPktSize = m_iMSS - 28;
            m_iPayloadSize = m_iPktSize - Packet.m_iPktHdrSize;

            // Prepare all structures
            try
            {
                m_pSndBuffer = new SndBuffer(32, m_iPayloadSize);
                m_pRcvBuffer = new RcvBuffer(m_iRcvBufSize);
                m_pSndLossList = new SndLossList(m_iFlowWindowSize * 2);
                m_pRcvLossList = new RcvLossList(m_iFlightFlagSize);
                m_pACKWindow = new ACKWindow(1024);
                m_pRcvTimeWindow = new PktTimeWindow(16, 64);
                m_pSndTimeWindow = new PktTimeWindow();
            }
            catch (Exception e)
            {
                throw new UdtException(3, 2, 0);
            }

            InfoBlock ib = new InfoBlock(peer.Address);
            if (m_pCache.TryGetValue(ib, out ib))
            {
                m_iRTT = ib.m_iRTT;
                m_iBandwidth = ib.m_iBandwidth;
            }

            m_pCC = m_pCCFactory.create();
            m_pCC.m_UDT = m_SocketID;
            m_pCC.setMSS(m_iMSS);
            m_pCC.setMaxCWndSize(m_iFlowWindowSize);
            m_pCC.setSndCurrSeqNo(m_iSndCurrSeqNo);
            m_pCC.setRcvRate(m_iDeliveryRate);
            m_pCC.setRTT(m_iRTT);
            m_pCC.setBandwidth(m_iBandwidth);
            m_pCC.init();

            m_ullInterval = (ulong)(m_pCC.m_dPktSndPeriod * m_ullCPUFrequency);
            m_dCongestionWindow = m_pCC.m_dCWndSize;

            m_pPeerAddr = peer;

            // And of course, it is connected.
            m_bConnected = true;

            // register this socket for receiving data packets
            m_pRNode.m_bOnList = true;
            m_pRcvQueue.setNewEntry(this);

            //send the response to the peer, see listen() for more discussions about this
            Packet response = new Packet();
            response.pack(hs);
            response.SetId(m_PeerID);
            m_pSndQueue.sendto(peer, response);
        }

        public unsafe void close()
        {
            if (!m_bOpened)
                return;

            if (m_Linger.Enabled)
            {
                ulong entertime = Timer.getTime();

                while (!m_bBroken && m_bConnected && (m_pSndBuffer.getCurrBufSize() > 0) && (Timer.getTime() - entertime < (ulong)m_Linger.LingerTime * 1000000))
                {
                    // linger has been checked by previous close() call and has expired
                    if (m_ullLingerExpiration >= entertime)
                        break;

                    if (!m_bSynSending)
                    {
                        // if this socket enables asynchronous sending, return immediately and let GC to close it later
                        if (0 == m_ullLingerExpiration)
                            m_ullLingerExpiration = entertime + (ulong)m_Linger.LingerTime * 1000000;

                        return;
                    }

                    System.Threading.Thread.Sleep(1);
                }
            }

            // remove this socket from the snd queue
            if (m_bConnected)
                m_pSndQueue.m_pSndUList.remove(this);

            // trigger any pending IO events.
            //s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, EPOLLOpt.UDT_EPOLL_ERR, true);
            // then remove itself from all epoll monitoring
            //try
            //{
            //    for (set<int>.iterator i = m_sPollID.begin(); i != m_sPollID.end(); ++i)
            //        s_UDTUnited.m_EPoll.remove_usock(* i, m_SocketID);
            //}
            //catch (Exception e)
            //{
            //}

            if (!m_bOpened)
                return;

            // Inform the threads handler to stop.
            m_bClosing = true;

            lock (m_ConnectionLock)
            {
                close_unsafe();
            }

            // waiting all send and recv calls to stop
            lock (m_SendLock) lock(m_RecvLock)
            { }

            // CLOSED.
            m_bOpened = false;
        }

        unsafe void close_unsafe()
        {
            // Signal the sender and recver if they are waiting for data.
            releaseSynch();

            if (m_bListening)
            {
                m_bListening = false;
                m_pRcvQueue.removeListener(this);
            }
            else if (m_bConnecting)
            {
                m_pRcvQueue.removeConnector(m_SocketID);
            }

            if (m_bConnected)
            {
                if (!m_bShutdown)
                    sendCtrl(5);

                m_pCC.close();

                // Store current connection information.
                InfoBlock ib = new InfoBlock(m_pPeerAddr.Address);
                if (!m_pCache.Add(ib))
                {
                    m_pCache.TryGetValue(ib, out ib);
                }
                ib.m_iRTT = m_iRTT;
                ib.m_iBandwidth = m_iBandwidth;

                m_bConnected = false;
            }

        }

        public int send(byte[] data, int offset, int len)
        {

            if (SocketType.Dgram == m_iSockType)
                return sendmsg(data, offset, len, 255, true);
                //throw new UdtException(5, 10, 0);

            // throw an exception if not connected
            if (m_bBroken || m_bClosing)
                throw new UdtException(2, 1, 0);
            else if (!m_bConnected)
                throw new UdtException(2, 2, 0);

            if (len <= 0)
                return 0;

            if (offset + len > data.Length)
                len = data.Length - offset;

            lock (m_SendLock)
            {
                return send_unsafe(data, offset, len);
            }
        }

        int send_unsafe(byte[] data, int offset, int len)
        {
            if (m_pSndBuffer.getCurrBufSize() == 0)
            {
                // delay the EXP timer to avoid mis-fired timeout
                ulong currtime = Timer.rdtsc();
                m_ullLastRspTime = currtime;
            }

            if (m_iSndBufSize <= m_pSndBuffer.getCurrBufSize())
            {
                if (!m_bSynSending)
                    throw new UdtException(6, 1, 0);
                else
                {
                    // wait here during a blocking sending

                    if (m_iSndTimeOut < 0)
                    {
                        while (!m_bBroken && m_bConnected && !m_bClosing && (m_iSndBufSize <= m_pSndBuffer.getCurrBufSize()) && m_bPeerHealth)
                            m_SendBlockCond.WaitOne(Timeout.Infinite);
                    }
                    else
                    {
                        ulong exptime = Timer.getTime() + (ulong)m_iSndTimeOut * 1000;

                        while (!m_bBroken && m_bConnected && !m_bClosing && (m_iSndBufSize <= m_pSndBuffer.getCurrBufSize()) && m_bPeerHealth && (Timer.getTime() < exptime))
                            m_SendBlockCond.WaitOne((int)(exptime - Timer.getTime()) / 1000);
                    }

                    // check the connection status
                    if (m_bBroken || m_bClosing)
                        throw new UdtException(2, 1, 0);
                    else if (!m_bConnected)
                        throw new UdtException(2, 2, 0);
                    else if (!m_bPeerHealth)
                    {
                        m_bPeerHealth = true;
                        throw new UdtException(7);
                    }
                }
            }

            if (m_iSndBufSize <= m_pSndBuffer.getCurrBufSize())
            {
                if (m_iSndTimeOut >= 0)
                    throw new UdtException(6, 3, 0);

                return 0;
            }

            int size = (m_iSndBufSize - m_pSndBuffer.getCurrBufSize()) * m_iPayloadSize;
            if (size > len)
                size = len;

            // record total time used for sending
            if (0 == m_pSndBuffer.getCurrBufSize())
                m_llSndDurationCounter = (long)Timer.getTime();

            // insert the user buffer into the sening list
            m_pSndBuffer.addBuffer(data, offset, size);

            // insert this socket to snd list if it is not on the list yet
            m_pSndQueue.m_pSndUList.update(this, false);

            //if (m_iSndBufSize <= m_pSndBuffer.getCurrBufSize())
            //{
            //    // write is not available any more
            //    s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, EPOLLOpt.UDT_EPOLL_OUT, false);
            //}

            return size;
        }

        public int recv(byte[] data, int offset, int len)
        {
            if (SocketType.Dgram == m_iSockType)
                throw new UdtException(5, 10, 0);
                

            // throw an exception if not connected
            if (!m_bConnected)
                throw new UdtException(2, 2, 0);
            else if ((m_bBroken || m_bClosing) && (0 == m_pRcvBuffer.getRcvDataSize()))
                throw new UdtException(2, 1, 0);

            if (len <= 0)
                return 0;

            lock (m_RecvLock)
            {
                return recv_unsafe(data, offset, len);
            }
        }

        int recv_unsafe(byte[] data, int offset, int len)
        {
            if (0 == m_pRcvBuffer.getRcvDataSize())
            {
                if (!m_bSynRecving)
                    throw new UdtException(6, 2, 0);
                else
                {
                    if (m_iRcvTimeOut < 0)
                    {
                        while (!m_bBroken && m_bConnected && !m_bClosing && (0 == m_pRcvBuffer.getRcvDataSize()))
                            m_RecvDataCond.WaitOne(Timeout.Infinite);
                    }
                    else
                    {
                        ulong enter_time = Timer.getTime();

                        while (!m_bBroken && m_bConnected && !m_bClosing && (0 == m_pRcvBuffer.getRcvDataSize()))
                        {
                            int diff = (int)(Timer.getTime() - enter_time) / 1000;
                            if (diff >= m_iRcvTimeOut)
                                break;
                            m_RecvDataCond.WaitOne(m_iRcvTimeOut - diff);
                        }
                    }
                }
            }

            // throw an exception if not connected
            if (!m_bConnected)
                throw new UdtException(2, 2, 0);
            else if ((m_bBroken || m_bClosing) && (0 == m_pRcvBuffer.getRcvDataSize()))
                throw new UdtException(2, 1, 0);

            int res = m_pRcvBuffer.readBuffer(data, offset, len);

            //if (m_pRcvBuffer.getRcvDataSize() <= 0)
            //{
            //    // read is not available any more
            //    s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false);
            //}

            if ((res <= 0) && (m_iRcvTimeOut >= 0))
                throw new UdtException(6, 3, 0);

            return res;
        }

        public int sendmsg(byte[] data, int offset, int len, int msttl, bool inorder)
        {
            if (SocketType.Stream == m_iSockType)
                throw new UdtException(5, 9, 0);

            // throw an exception if not connected
            if (m_bBroken || m_bClosing)
                throw new UdtException(2, 1, 0);
            else if (!m_bConnected)
                throw new UdtException(2, 2, 0);

            if (len <= 0)
                return 0;

            if (len + offset > data.Length)
                len = data.Length - offset;

            if (len > m_iSndBufSize * m_iPayloadSize)
                throw new UdtException(5, 12, 0);

            lock (m_SendLock)
            {
                return sendmsg_unsafe(data, offset, len, msttl, inorder);
            }
        }

        int sendmsg_unsafe(byte[] data, int offset, int len, int msttl, bool inorder)
        {
            if (m_pSndBuffer.getCurrBufSize() == 0)
            {
                // delay the EXP timer to avoid mis-fired timeout
                m_ullLastRspTime = Timer.rdtsc();
            }

            if ((m_iSndBufSize - m_pSndBuffer.getCurrBufSize()) * m_iPayloadSize < len)
            {
                if (!m_bSynSending)
                    throw new UdtException(6, 1, 0);
                else
                {
                    // wait here during a blocking sending
                    if (m_iSndTimeOut < 0)
                    {
                        while (!m_bBroken && m_bConnected && !m_bClosing && ((m_iSndBufSize - m_pSndBuffer.getCurrBufSize()) * m_iPayloadSize < len))
                            m_SendBlockCond.WaitOne(Timeout.Infinite);
                    }
                    else
                    {
                        ulong exptime = Timer.getTime() + (ulong)m_iSndTimeOut * 1000;

                        while (!m_bBroken && m_bConnected && !m_bClosing && ((m_iSndBufSize - m_pSndBuffer.getCurrBufSize()) * m_iPayloadSize < len) && (Timer.getTime() < exptime))
                            m_SendBlockCond.WaitOne((int)(exptime - Timer.getTime()) / 1000);
                    }

                    // check the connection status
                    if (m_bBroken || m_bClosing)
                        throw new UdtException(2, 1, 0);
                    else if (!m_bConnected)
                        throw new UdtException(2, 2, 0);
                }
            }

            if ((m_iSndBufSize - m_pSndBuffer.getCurrBufSize()) * m_iPayloadSize < len)
            {
                if (m_iSndTimeOut >= 0)
                    throw new UdtException(6, 3, 0);

                return 0;
            }

            // record total time used for sending
            if (0 == m_pSndBuffer.getCurrBufSize())
                m_llSndDurationCounter = (long)Timer.getTime();

            // insert the user buffer into the sening list
            m_pSndBuffer.addBuffer(data, offset, len, msttl, inorder);

            // insert this socket to the snd list if it is not on the list yet
            m_pSndQueue.m_pSndUList.update(this, false);

            //if (m_iSndBufSize <= m_pSndBuffer.getCurrBufSize())
            //{
            //    // write is not available any more
            //    s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, false);
            //}

            return len;
        }

        public int recvmsg(byte[] data, int len)
        {
            if (SocketType.Stream == m_iSockType)
                throw new UdtException(5, 9, 0);

            // throw an exception if not connected
            if (!m_bConnected)
                throw new UdtException(2, 2, 0);

            if (len <= 0)
                return 0;

            lock (m_RecvLock)
            {
                return recvmsg_unsafe(data, len);
            }
        }

        int recvmsg_unsafe(byte[] data, int len)
        {
            int res = 0;
            if (m_bBroken || m_bClosing)
            {
                res = m_pRcvBuffer.readMsg(data, len);

                //if (m_pRcvBuffer.getRcvMsgNum() <= 0)
                //{
                //    // read is not available any more
                //    s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, EPOLLOpt.UDT_EPOLL_IN, false);
                //}

                if (0 == res)
                    throw new UdtException(2, 1, 0);
                else
                    return res;
            }

            if (!m_bSynRecving)
            {
                res = m_pRcvBuffer.readMsg(data, len);
                if (0 == res)
                    throw new UdtException(6, 2, 0);
                else
                    return res;
            }

            bool timeout = false;

            do
            {
                if (m_iRcvTimeOut < 0)
                {
                    while (!m_bBroken && m_bConnected && !m_bClosing && (0 == (res = m_pRcvBuffer.readMsg(data, len))))
                        m_RecvDataCond.WaitOne(Timeout.Infinite);
                }
                else
                {
                    timeout = !m_RecvDataCond.WaitOne(m_iRcvTimeOut);
                    res = m_pRcvBuffer.readMsg(data, len);
                }

                if (m_bBroken || m_bClosing)
                    throw new UdtException(2, 1, 0);
                else if (!m_bConnected)
                    throw new UdtException(2, 2, 0);
            }
            while ((0 == res) && !timeout);

            //if (m_pRcvBuffer.getRcvMsgNum() <= 0)
            //{
            //    // read is not available any more
            //    s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false);
            //}

            if ((res <= 0) && (m_iRcvTimeOut >= 0))
                throw new UdtException(6, 3, 0);

            return res;
        }

        public void sample(PerfMon perf, bool clear)
        {
            if (!m_bConnected)
                throw new UdtException(2, 2, 0);
            if (m_bBroken || m_bClosing)
                throw new UdtException(2, 1, 0);

            ulong currtime = Timer.getTime();
            perf.msTimeStamp = (long)(currtime - m_StartTime) / 1000;

            perf.pktSent = m_llTraceSent;
            perf.pktRecv = m_llTraceRecv;
            perf.pktSndLoss = m_iTraceSndLoss;
            perf.pktRcvLoss = m_iTraceRcvLoss;
            perf.pktRetrans = m_iTraceRetrans;
            perf.pktSentACK = m_iSentACK;
            perf.pktRecvACK = m_iRecvACK;
            perf.pktSentNAK = m_iSentNAK;
            perf.pktRecvNAK = m_iRecvNAK;
            perf.usSndDuration = m_llSndDuration;

            perf.pktSentTotal = m_llSentTotal;
            perf.pktRecvTotal = m_llRecvTotal;
            perf.pktSndLossTotal = m_iSndLossTotal;
            perf.pktRcvLossTotal = m_iRcvLossTotal;
            perf.pktRetransTotal = m_iRetransTotal;
            perf.pktSentACKTotal = m_iSentACKTotal;
            perf.pktRecvACKTotal = m_iRecvACKTotal;
            perf.pktSentNAKTotal = m_iSentNAKTotal;
            perf.pktRecvNAKTotal = m_iRecvNAKTotal;
            perf.usSndDurationTotal = m_llSndDurationTotal;

            double interval = (double)(currtime - m_LastSampleTime);

            perf.mbpsSendRate = (double)(m_llTraceSent) * m_iPayloadSize * 8.0 / interval;
            perf.mbpsRecvRate = (double)(m_llTraceRecv) * m_iPayloadSize * 8.0 / interval;

            perf.usPktSndPeriod = m_ullInterval / (double)m_ullCPUFrequency;
            perf.pktFlowWindow = m_iFlowWindowSize;
            perf.pktCongestionWindow = (int)m_dCongestionWindow;
            perf.pktFlightSize = SequenceNumber.seqlen(m_iSndLastAck, SequenceNumber.incseq(m_iSndCurrSeqNo)) - 1;
            perf.msRTT = m_iRTT / 1000.0;
            perf.mbpsBandwidth = m_iBandwidth * m_iPayloadSize * 8.0 / 1000000.0;

            if (Monitor.TryEnter(m_ConnectionLock))
            {
                perf.byteAvailSndBuf = (null == m_pSndBuffer) ? 0 : (m_iSndBufSize - m_pSndBuffer.getCurrBufSize()) * m_iMSS;
                perf.byteAvailRcvBuf = (null == m_pRcvBuffer) ? 0 : m_pRcvBuffer.getAvailBufSize() * m_iMSS;

                Monitor.Exit(m_ConnectionLock);
            }
            else
            {
                perf.byteAvailSndBuf = 0;
                perf.byteAvailRcvBuf = 0;
            }

            if (clear)
            {
                m_llTraceSent = m_llTraceRecv = m_iTraceSndLoss = m_iTraceRcvLoss = m_iTraceRetrans = m_iSentACK = m_iRecvACK = m_iSentNAK = m_iRecvNAK = 0;
                m_llSndDuration = 0;
                m_LastSampleTime = currtime;
            }
        }

        void CCUpdate()
        {
            m_ullInterval = (ulong)(m_pCC.m_dPktSndPeriod * m_ullCPUFrequency);
            m_dCongestionWindow = m_pCC.m_dCWndSize;

            if (m_llMaxBW <= 0)
                return;
            double minSP = 1000000.0 / ((double)m_llMaxBW / m_iMSS) * m_ullCPUFrequency;
            if (m_ullInterval < minSP)
                m_ullInterval = (ulong)minSP;
        }


        void destroySynch()
        {
            m_SendBlockCond.Close();
            m_RecvDataCond.Close();
        }

        void releaseSynch()
        {
            m_SendBlockCond.Set();

            bool gotLock = false;
            try
            {
                Monitor.Enter(m_SendLock, ref gotLock);
            }
            finally
            {
                if (gotLock)
                    Monitor.Exit(m_SendLock);
            }

            m_RecvDataCond.Set();

            gotLock = false;
            try
            {
                Monitor.Enter(m_RecvLock, ref gotLock);
            }
            finally
            {
                if (gotLock)
                    Monitor.Exit(m_RecvLock);
            }
        }

        unsafe void sendCtrl(int pkttype, void* lparam = null, void* rparam = null, int size = 0)
        {
            Packet ctrlpkt = new Packet();

            switch (pkttype)
            {
                case 2: //010 - Acknowledgement
                    {
                        int ack;

                        // If there is no loss, the ACK is the current largest sequence number plus 1;
                        // Otherwise it is the smallest sequence number in the receiver loss list.
                        if (0 == m_pRcvLossList.getLossLength())
                            ack = SequenceNumber.incseq(m_iRcvCurrSeqNo);
                        else
                            ack = m_pRcvLossList.getFirstLostSeq();

                        if (ack == m_iRcvLastAckAck)
                            break;

                        // send out a lite ACK
                        // to save time on buffer processing and bandwidth/AS measurement, a lite ACK only feeds back an ACK number
                        if (4 == size)
                        {
                            ctrlpkt.pack(pkttype, null, &ack, size);
                            ctrlpkt.SetId(m_PeerID);
                            m_pSndQueue.sendto(m_pPeerAddr, ctrlpkt);

                            break;
                        }

                        ulong currtime = Timer.rdtsc();

                        // There are new received packets to acknowledge, update related information.
                        if (SequenceNumber.seqcmp(ack, m_iRcvLastAck) > 0)
                        {
                            int acksize = SequenceNumber.seqoff(m_iRcvLastAck, ack);

                            m_iRcvLastAck = ack;

                            m_pRcvBuffer.ackData(acksize);

                            // signal a waiting "recv" call if there is any data available
                            if (m_bSynRecving)
                                m_RecvDataCond.Set();

                            // acknowledge any waiting epolls to read
                            //s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, EPOLLOpt.UDT_EPOLL_IN, true);
                        }
                        else if (ack == m_iRcvLastAck)
                        {
                            if ((currtime - m_ullLastAckTime) < ((ulong)(m_iRTT + 4 * m_iRTTVar) * m_ullCPUFrequency))
                                break;
                        }
                        else
                            break;

                        // Send out the ACK only if has not been received by the sender before
                        if (SequenceNumber.seqcmp(m_iRcvLastAck, m_iRcvLastAckAck) > 0)
                        {
                            int[] data = new int[6];

                            m_iAckSeqNo = AckNumber.incack(m_iAckSeqNo);
                            data[0] = m_iRcvLastAck;
                            data[1] = m_iRTT;
                            data[2] = m_iRTTVar;
                            data[3] = m_pRcvBuffer.getAvailBufSize();
                            // a minimum flow window of 2 is used, even if buffer is full, to break potential deadlock
                            if (data[3] < 2)
                                data[3] = 2;

                            if (currtime - m_ullLastAckTime > m_ullSYNInt)
                            {
                                data[4] = m_pRcvTimeWindow.getPktRcvSpeed();
                                data[5] = m_pRcvTimeWindow.getBandwidth();
                                ctrlpkt.pack(pkttype, m_iAckSeqNo, data);

                                m_ullLastAckTime = Timer.rdtsc();
                            }
                            else
                            {
                                ctrlpkt.pack(pkttype, m_iAckSeqNo, data, 4);
                            }

                            ctrlpkt.SetId(m_PeerID);
                            m_pSndQueue.sendto(m_pPeerAddr, ctrlpkt);

                            m_pACKWindow.store(m_iAckSeqNo, m_iRcvLastAck);

                            ++m_iSentACK;
                            ++m_iSentACKTotal;
                        }

                        break;
                    }

                case 6: //110 - Acknowledgement of Acknowledgement
                    ctrlpkt.pack(pkttype, lparam);
                    ctrlpkt.SetId(m_PeerID);
                    m_pSndQueue.sendto(m_pPeerAddr, ctrlpkt);

                    break;

                case 3: //011 - Loss Report
                    {
                        if (null != rparam)
                        {
                            if (1 == size)
                            {
                                // only 1 loss packet
                                ctrlpkt.pack(pkttype, null, (int*)rparam + 1, 4);
                            }
                            else
                            {
                                // more than 1 loss packets
                                ctrlpkt.pack(pkttype, null, rparam, 8);
                            }

                            ctrlpkt.SetId(m_PeerID);
                            m_pSndQueue.sendto(m_pPeerAddr, ctrlpkt);

                            ++m_iSentNAK;
                            ++m_iSentNAKTotal;
                        }
                        else if (m_pRcvLossList.getLossLength() > 0)
                        {
                            // this is periodically NAK report; make sure NAK cannot be sent back too often

                            // read loss list from the local receiver loss list
                            int[] data = new int[m_iPayloadSize / 4];
                            int losslen;
                            m_pRcvLossList.getLossArray(data, out losslen, m_iPayloadSize / 4);

                            if (0 < losslen)
                            {
                                ctrlpkt.pack(pkttype, data, losslen);
                                ctrlpkt.SetId(m_PeerID);
                                m_pSndQueue.sendto(m_pPeerAddr, ctrlpkt);

                                ++m_iSentNAK;
                                ++m_iSentNAKTotal;
                            }
                        }

                        // update next NAK time, which should wait enough time for the retansmission, but not too long
                        m_ullNAKInt = (ulong)(m_iRTT + 4 * m_iRTTVar) * m_ullCPUFrequency;
                        int rcv_speed = m_pRcvTimeWindow.getPktRcvSpeed();
                        if (rcv_speed > 0)
                            m_ullNAKInt += (ulong)(m_pRcvLossList.getLossLength() * 1000000 / rcv_speed) *m_ullCPUFrequency;
                        if (m_ullNAKInt < m_ullMinNakInt)
                            m_ullNAKInt = m_ullMinNakInt;

                        break;
                    }

                case 4: //100 - Congestion Warning
                    ctrlpkt.pack(pkttype);
                    ctrlpkt.SetId(m_PeerID);
                    m_pSndQueue.sendto(m_pPeerAddr, ctrlpkt);

                    m_ullLastWarningTime = Timer.rdtsc();
                    break;

                case 1: //001 - Keep-alive
                    ctrlpkt.pack(pkttype);
                    ctrlpkt.SetId(m_PeerID);
                    m_pSndQueue.sendto(m_pPeerAddr, ctrlpkt);

                    break;

                case 0: //000 - Handshake
                    ctrlpkt.pack(pkttype, null, rparam, Handshake.m_iContentSize);
                    ctrlpkt.SetId(m_PeerID);
                    m_pSndQueue.sendto(m_pPeerAddr, ctrlpkt);

                    break;

                case 5: //101 - Shutdown
                    ctrlpkt.pack(pkttype);
                    ctrlpkt.SetId(m_PeerID);
                    m_pSndQueue.sendto(m_pPeerAddr, ctrlpkt);

                    break;

                case 7: //111 - Msg drop request
                    ctrlpkt.pack(pkttype, lparam, rparam, 8);
                    ctrlpkt.SetId(m_PeerID);
                    m_pSndQueue.sendto(m_pPeerAddr, ctrlpkt);

                    break;

                case 8: //1000 - acknowledge the peer side a special error
                    ctrlpkt.pack(pkttype, lparam);
                    ctrlpkt.SetId(m_PeerID);
                    m_pSndQueue.sendto(m_pPeerAddr, ctrlpkt);

                    break;

                case 32767: //0x7FFF - Resevered for future use
                    break;

                default:
                    break;
            }
        }

        public unsafe void processCtrl(Packet ctrlpkt)
        {
            // Just heard from the peer, reset the expiration count.
            m_iEXPCount = 1;
            ulong currtime = Timer.rdtsc();
            m_ullLastRspTime = currtime;

            switch (ctrlpkt.getType())
            {
                case 2: //010 - Acknowledgement
                    {
                        int ack;

                        // process a lite ACK
                        if (4 == ctrlpkt.getLength())
                        {
                            ack = ctrlpkt.GetIntFromData(0);
                            if (SequenceNumber.seqcmp(ack, m_iSndLastAck) >= 0)
                            {
                                m_iFlowWindowSize -= SequenceNumber.seqoff(m_iSndLastAck, ack);
                                m_iSndLastAck = ack;
                            }

                            break;
                        }

                        // read ACK seq. no.
                        ack = ctrlpkt.getAckSeqNo();

                        // send ACK acknowledgement
                        // number of ACK2 can be much less than number of ACK
                        ulong now = Timer.getTime();
                        if ((now - m_ullSndLastAck2Time > m_iSYNInterval) || (ack == m_iSndLastAck2))
                        {
                            sendCtrl(6, &ack);
                            m_iSndLastAck2 = ack;
                            m_ullSndLastAck2Time = now;
                        }

                        // Got data ACK
                        ack = ctrlpkt.GetIntFromData(0);

                        // check the validation of the ack
                        if (SequenceNumber.seqcmp(ack, SequenceNumber.incseq(m_iSndCurrSeqNo)) > 0)
                        {
                            //this should not happen: attack or bug
                            m_bBroken = true;
                            m_iBrokenCounter = 0;
                            break;
                        }

                        if (SequenceNumber.seqcmp(ack, m_iSndLastAck) >= 0)
                        {
                            // Update Flow Window Size, must update before and together with m_iSndLastAck
                            m_iFlowWindowSize = ctrlpkt.GetIntFromData(3);
                            m_iSndLastAck = ack;
                        }

                        // protect packet retransmission
                        bool bLockTaken = false;
                        Monitor.Enter(m_AckLock, ref bLockTaken);

                        int offset = SequenceNumber.seqoff(m_iSndLastDataAck, ack);
                        if (offset <= 0)
                        {
                            // discard it if it is a repeated ACK
                            if (bLockTaken)
                                Monitor.Exit(m_AckLock);
                            break;
                        }

                        // acknowledge the sending buffer
                        m_pSndBuffer.ackData(offset);

                        // record total time used for sending
                        m_llSndDuration += (long)now - m_llSndDurationCounter;
                        m_llSndDurationTotal += (long)now - m_llSndDurationCounter;
                        m_llSndDurationCounter = (long)now;

                        // update sending variables
                        m_iSndLastDataAck = ack;
                        m_pSndLossList.remove(SequenceNumber.decseq(m_iSndLastDataAck));

                        if (bLockTaken)
                            Monitor.Exit(m_AckLock);

                        if (m_bSynSending)
                            m_SendBlockCond.Set();

                        // acknowledde any waiting epolls to write
                        //s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, EPOLLOpt.UDT_EPOLL_OUT, true);

                        // insert this socket to snd list if it is not on the list yet
                        m_pSndQueue.m_pSndUList.update(this, false);

                        // Update RTT
                        int rtt = ctrlpkt.GetIntFromData(1);
                        m_iRTTVar = (m_iRTTVar * 3 + Math.Abs(rtt - m_iRTT)) >> 2;
                        m_iRTT = (m_iRTT * 7 + rtt) >> 3;

                        m_pCC.setRTT(m_iRTT);

                        if (ctrlpkt.getLength() > 16)
                        {
                            // Update Estimated Bandwidth and packet delivery rate
                            if (ctrlpkt.GetIntFromData(4) > 0)
                                m_iDeliveryRate = (m_iDeliveryRate * 7 + ctrlpkt.GetIntFromData(4)) >> 3;

                            if (ctrlpkt.GetIntFromData(5) > 0)
                                m_iBandwidth = (m_iBandwidth * 7 + ctrlpkt.GetIntFromData(5)) >> 3;

                            m_pCC.setRcvRate(m_iDeliveryRate);
                            m_pCC.setBandwidth(m_iBandwidth);
                        }

                        m_pCC.onACK(ack);
                        CCUpdate();

                        ++m_iRecvACK;
                        ++m_iRecvACKTotal;

                        break;
                    }

                case 6: //110 - Acknowledgement of Acknowledgement
                    {
                        int ack = -1;
                        int rtt = -1;

                        // update RTT
                        rtt = m_pACKWindow.acknowledge(ctrlpkt.getAckSeqNo(), ref ack);
                        if (rtt <= 0)
                            break;

                        //if increasing delay detected...
                        //   sendCtrl(4);

                        // RTT EWMA
                        m_iRTTVar = (m_iRTTVar * 3 + Math.Abs(rtt - m_iRTT)) >> 2;
                        m_iRTT = (m_iRTT * 7 + rtt) >> 3;

                        m_pCC.setRTT(m_iRTT);

                        // update last ACK that has been received by the sender
                        if (SequenceNumber.seqcmp(ack, m_iRcvLastAckAck) > 0)
                            m_iRcvLastAckAck = ack;

                        break;
                    }

                case 3: //011 - Loss Report
                    {
                        int[] losslist = new int[ctrlpkt.getLength() / 4];
                        Buffer.BlockCopy(ctrlpkt.GetDataBytes(), 0, losslist, 0, ctrlpkt.getLength());

                        m_pCC.onLoss(losslist, ctrlpkt.getLength() / 4);
                        CCUpdate();

                        bool secure = true;

                        // decode loss list message and insert loss into the sender loss list
                        for (int i = 0; i < losslist.Length; ++i)
                        {
                            if (0 != (losslist[i] & 0x80000000))
                            {
                                if ((SequenceNumber.seqcmp(losslist[i] & 0x7FFFFFFF, losslist[i + 1]) > 0) || (SequenceNumber.seqcmp(losslist[i + 1], m_iSndCurrSeqNo) > 0))
                                {
                                    // seq_a must not be greater than seq_b; seq_b must not be greater than the most recent sent seq
                                    secure = false;
                                    break;
                                }

                                int num = 0;
                                if (SequenceNumber.seqcmp(losslist[i] & 0x7FFFFFFF, m_iSndLastAck) >= 0)
                                    num = m_pSndLossList.insert(losslist[i] & 0x7FFFFFFF, losslist[i + 1]);
                                else if (SequenceNumber.seqcmp(losslist[i + 1], m_iSndLastAck) >= 0)
                                    num = m_pSndLossList.insert(m_iSndLastAck, losslist[i + 1]);

                                m_iTraceSndLoss += num;
                                m_iSndLossTotal += num;

                                ++i;
                            }
                            else if (SequenceNumber.seqcmp(losslist[i], m_iSndLastAck) >= 0)
                            {
                                if (SequenceNumber.seqcmp(losslist[i], m_iSndCurrSeqNo) > 0)
                                {
                                    //seq_a must not be greater than the most recent sent seq
                                    secure = false;
                                    break;
                                }

                                int num = m_pSndLossList.insert(losslist[i], losslist[i]);

                                m_iTraceSndLoss += num;
                                m_iSndLossTotal += num;
                            }
                        }

                        if (!secure)
                        {
                            //this should not happen: attack or bug
                            m_bBroken = true;
                            m_iBrokenCounter = 0;
                            break;
                        }

                        // the lost packet (retransmission) should be sent out immediately
                        m_pSndQueue.m_pSndUList.update(this);

                        ++m_iRecvNAK;
                        ++m_iRecvNAKTotal;

                        break;
                    }

                case 4: //100 - Delay Warning
                        // One way packet delay is increasing, so decrease the sending rate
                    m_ullInterval = (ulong)Math.Ceiling(m_ullInterval * 1.125);
                    m_iLastDecSeq = m_iSndCurrSeqNo;

                    break;

                case 1: //001 - Keep-alive
                        // The only purpose of keep-alive packet is to tell that the peer is still alive
                        // nothing needs to be done.

                    break;

                case 0: //000 - Handshake
                    {
                        Handshake req = new Handshake();
                        req.deserialize(ctrlpkt.GetDataBytes(), ctrlpkt.getLength());
                        if ((req.m_iReqType > 0) || (m_bRendezvous && (req.m_iReqType != -2)))
                        {
                            // The peer side has not received the handshake message, so it keeps querying
                            // resend the handshake packet

                            Handshake initdata = new Handshake();
                            initdata.m_iISN = m_iISN;
                            initdata.m_iMSS = m_iMSS;
                            initdata.m_iFlightFlagSize = m_iFlightFlagSize;
                            initdata.m_iReqType = (!m_bRendezvous) ? -1 : -2;
                            initdata.m_iID = m_SocketID;

                            byte[] hs = new byte[m_iPayloadSize];
                            int hs_size = m_iPayloadSize;
                            initdata.serialize(hs);
                            fixed (byte* pHS = hs)
                            {
                                sendCtrl(0, null, pHS, hs_size);
                            }
                        }

                        break;
                    }

                case 5: //101 - Shutdown
                    m_bShutdown = true;
                    m_bClosing = true;
                    m_bBroken = true;
                    m_iBrokenCounter = 60;

                    // Signal the sender and recver if they are waiting for data.
                    releaseSynch();

                    Timer.triggerEvent();

                    break;

                case 7: //111 - Msg drop request
                    m_pRcvBuffer.dropMsg(ctrlpkt.getMsgSeq());
                    m_pRcvLossList.remove(ctrlpkt.GetIntFromData(0), ctrlpkt.GetIntFromData(1));

                    // move forward with current recv seq no.
                    if ((SequenceNumber.seqcmp(ctrlpkt.GetIntFromData(0), SequenceNumber.incseq(m_iRcvCurrSeqNo)) <= 0)
                       && (SequenceNumber.seqcmp(ctrlpkt.GetIntFromData(1), m_iRcvCurrSeqNo) > 0))
                    {
                        m_iRcvCurrSeqNo = ctrlpkt.GetIntFromData(1);
                    }

                    break;

                case 8: // 1000 - An error has happened to the peer side
                        //int err_type = packet.getAddInfo();

                    // currently only this error is signalled from the peer side
                    // if recvfile() failes (e.g., due to disk fail), blcoked sendfile/send should return immediately
                    // giving the app a chance to fix the issue

                    m_bPeerHealth = false;

                    break;

                case 32767: //0x7FFF - reserved and user defined messages
                    m_pCC.processCustomMsg(ctrlpkt);
                    CCUpdate();

                    break;

                default:
                    break;
            }
        }

        public unsafe int packData(Packet packet, ref ulong ts)
        {
            int payload = 0;
            bool probe = false;

            ulong entertime = Timer.rdtsc();

            if ((0 != m_ullTargetTime) && (entertime > m_ullTargetTime))
                m_ullTimeDiff += entertime - m_ullTargetTime;

            // Loss retransmission always has higher priority.
            packet.SetSequenceNumber(m_pSndLossList.getLostSeq());
            if (packet.GetSequenceNumber() >= 0)
            {
                // protect m_iSndLastDataAck from updating by ACK processing
                lock (m_AckLock)
                {
                    int offset = SequenceNumber.seqoff(m_iSndLastDataAck, packet.GetSequenceNumber());
                    if (offset < 0)
                        return 0;

                    int msglen = 0;
                    byte[] data = null;
                    uint msgNo = 0;
                    payload = m_pSndBuffer.readData(ref data, offset, ref msgNo, out msglen);
                    packet.SetDataFromBytes(data, 0, payload);
                    packet.SetMessageNumber(msgNo);

                    if (-1 == payload)
                    {
                        int[] seqpair = new int[2];
                        seqpair[0] = packet.GetSequenceNumber();
                        seqpair[1] = SequenceNumber.incseq(seqpair[0], msglen);
                        msgNo = packet.GetMessageNumber();
                        fixed (int* pSeqpair = seqpair)
                        {
                            sendCtrl(7, &msgNo, pSeqpair, 8);
                        }

                        // only one msg drop request is necessary
                        m_pSndLossList.remove(seqpair[1]);

                        // skip all dropped packets
                        if (SequenceNumber.seqcmp(m_iSndCurrSeqNo, SequenceNumber.incseq(seqpair[1])) < 0)
                            m_iSndCurrSeqNo = SequenceNumber.incseq(seqpair[1]);

                        return 0;
                    }
                    else if (0 == payload)
                        return 0;

                    ++m_iTraceRetrans;
                    ++m_iRetransTotal;
                }
            }
            else
            {
                // If no loss, pack a new packet.

                // check congestion/flow window limit
                int cwnd = (m_iFlowWindowSize < (int)m_dCongestionWindow) ? m_iFlowWindowSize : (int)m_dCongestionWindow;
                if (cwnd >= SequenceNumber.seqlen(m_iSndLastAck, SequenceNumber.incseq(m_iSndCurrSeqNo)))
                {
                    byte[] data = null;
                    uint msgNo = 0;
                    payload = m_pSndBuffer.readData(ref data, ref msgNo);
                    if (0 != payload)
                    {
                        packet.SetDataFromBytes(data, 0, payload);
                        packet.SetMessageNumber(msgNo);

                        m_iSndCurrSeqNo = SequenceNumber.incseq(m_iSndCurrSeqNo);
                        m_pCC.setSndCurrSeqNo(m_iSndCurrSeqNo);

                        packet.SetSequenceNumber(m_iSndCurrSeqNo);

                        // every 16 (0xF) packets, a packet pair is sent
                        if (0 == (packet.GetSequenceNumber() & 0xF))
                            probe = true;
                    }
                    else
                    {
                        m_ullTargetTime = 0;
                        m_ullTimeDiff = 0;
                        ts = 0;
                        return 0;
                    }
                }
                else
                {
                    m_ullTargetTime = 0;
                    m_ullTimeDiff = 0;
                    ts = 0;
                    return 0;
                }
            }

            packet.SetTimestamp((int)(Timer.getTime() - m_StartTime));
            packet.SetId(m_PeerID);
            packet.setLength(payload);

            m_pCC.onPktSent(packet);
            m_pSndTimeWindow.onPktSent(packet.GetTimestamp());

            ++m_llTraceSent;
            ++m_llSentTotal;

            if (probe)
            {
                // sends out probing packet pair
                ts = entertime;
                probe = false;
            }
            else
            {
                if (m_ullTimeDiff >= m_ullInterval)
                {
                    ts = entertime;
                    m_ullTimeDiff -= m_ullInterval;
                }
                else
                {
                    ts = entertime + m_ullInterval - m_ullTimeDiff;
                    m_ullTimeDiff = 0;
                }
            }

            m_ullTargetTime = ts;

            return payload;
        }

        public unsafe int processData(Unit unit)
        {
            Packet packet = unit.m_Packet;

            // Just heard from the peer, reset the expiration count.
            m_iEXPCount = 1;
            ulong currtime = Timer.rdtsc();
            m_ullLastRspTime = currtime;

            m_pCC.onPktReceived(packet);
            ++m_iPktCount;
            // update time information
            m_pRcvTimeWindow.onPktArrival();

            // check if it is probing packet pair
            if (0 == (packet.GetSequenceNumber() & 0xF))
                m_pRcvTimeWindow.probe1Arrival();
            else if (1 == (packet.GetSequenceNumber() & 0xF))
                m_pRcvTimeWindow.probe2Arrival();

            ++m_llTraceRecv;
            ++m_llRecvTotal;

            int offset = SequenceNumber.seqoff(m_iRcvLastAck, packet.GetSequenceNumber());
            if ((offset < 0) || (offset >= m_pRcvBuffer.getAvailBufSize()))
                return -1;

            if (m_pRcvBuffer.addData(unit, offset) < 0)
                return -1;

            // Loss detection.
            if (SequenceNumber.seqcmp(packet.GetSequenceNumber(), SequenceNumber.incseq(m_iRcvCurrSeqNo)) > 0)
            {
                // If loss found, insert them to the receiver loss list
                m_pRcvLossList.insert(SequenceNumber.incseq(m_iRcvCurrSeqNo), SequenceNumber.decseq(packet.GetSequenceNumber()));

                // pack loss list for NAK
                int[] lossdata = new int[2];
                lossdata[0] = (int)(SequenceNumber.incseq(m_iRcvCurrSeqNo) | 0x80000000);
                lossdata[1] = SequenceNumber.decseq(packet.GetSequenceNumber());

                // Generate loss report immediately.
                fixed (int* pLossdata = lossdata)
                {
                    sendCtrl(3, null, pLossdata, (SequenceNumber.incseq(m_iRcvCurrSeqNo) == SequenceNumber.decseq(packet.GetSequenceNumber())) ? 1 : 2);
                }

                int loss = SequenceNumber.seqlen(m_iRcvCurrSeqNo, packet.GetSequenceNumber()) - 2;
                m_iTraceRcvLoss += loss;
                m_iRcvLossTotal += loss;
            }

            // This is not a regular fixed size packet...   
            //an irregular sized packet usually indicates the end of a message, so send an ACK immediately   
            if (packet.getLength() != m_iPayloadSize)
                m_ullNextACKTime = Timer.rdtsc();

            // Update the current largest sequence number that has been received.
            // Or it is a retransmitted packet, remove it from receiver loss list.
            if (SequenceNumber.seqcmp(packet.GetSequenceNumber(), m_iRcvCurrSeqNo) > 0)
                m_iRcvCurrSeqNo = packet.GetSequenceNumber();
            else
                m_pRcvLossList.remove(packet.GetSequenceNumber());

            return 0;
        }

        public int listen(IPEndPoint endPoint, Packet packet)
        {
            if (m_bClosing)
                return 1002;

            if (packet.getLength() != Handshake.m_iContentSize)
                return 1004;

            Handshake hs = new Handshake();
            hs.deserialize(packet.GetDataBytes(), packet.getLength());

            //IPHostEntry host = Dns.GetHostEntry(endPoint.Address); //TODO SocketException,ArgumentException
            IPHostEntry host = new IPHostEntry(){HostName = endPoint.Address.ToString() };
            // SYN cookie
            long timestamp = (long)(Timer.getTime() - m_StartTime) / 60000000;  // secret changes every one minute

            string cookiestr = string.Format("{0}:{1}:{2}", host.HostName, endPoint.Port, timestamp);

            MD5 md5 = MD5.Create();
            byte[] cookie = md5.ComputeHash(System.Text.Encoding.ASCII.GetBytes(cookiestr));

            if (1 == hs.m_iReqType)
            {
                hs.m_iCookie = BitConverter.ToInt32(cookie, 0);
                packet.pack(hs);
                packet.SetId(hs.m_iID);
                m_pSndQueue.sendto(endPoint, packet);
                return 0;
            }
            else
            {
                if (hs.m_iCookie != BitConverter.ToInt32(cookie, 0))
                {
                    timestamp--;
                    cookiestr = string.Format("{0}:{1}:{2}", host.HostName, endPoint.Port, timestamp);
                    cookie = md5.ComputeHash(System.Text.Encoding.ASCII.GetBytes(cookiestr));

                    if (hs.m_iCookie != BitConverter.ToInt32(cookie, 0))
                        return -1;
                }
            }

            int id = hs.m_iID;

            // When a peer side connects in...
            if ((1 == packet.getFlag()) && (0 == packet.getType()))
            {
                if ((hs.m_iVersion != m_iVersion) || (hs.m_iType != m_iSockType))
                {
                    // mismatch, reject the request
                    hs.m_iReqType = 1002;
                    packet.pack(hs);
                    packet.SetId(id);
                    m_pSndQueue.sendto(endPoint, packet);
                }
                else
                {
                    int result = s_UDTUnited.newConnection(m_SocketID, endPoint, hs);

                    if (result == -1)
                        hs.m_iReqType = 1002;

                    // send back a response if connection failed or connection already existed
                    // new connection response should be sent in connect()
                    if (result != 1)
                    {
                        packet.pack(hs);
                        packet.SetId(id);
                        m_pSndQueue.sendto(endPoint, packet);
                    }
                    else
                    {
                        // a new connection has been created, enable epoll for write 
                        //s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, EPOLLOpt.UDT_EPOLL_OUT, true);
                    }
                }
            }

            return hs.m_iReqType;
        }

        public unsafe void checkTimers()
        {
            // update CC parameters
            CCUpdate();
            //ulong minint = (ulong)(m_ullCPUFrequency * m_pSndTimeWindow.getMinPktSndInt() * 0.9);
            //if (m_ullInterval < minint)
            //   m_ullInterval = minint;

            ulong currtime = Timer.rdtsc();

            if ((currtime > m_ullNextACKTime) || ((m_pCC.m_iACKInterval > 0) && (m_pCC.m_iACKInterval <= m_iPktCount)))
            {
                // ACK timer expired or ACK interval is reached

                sendCtrl(2);
                currtime = Timer.rdtsc();
                if (m_pCC.m_iACKPeriod > 0)
                    m_ullNextACKTime = currtime + (ulong)m_pCC.m_iACKPeriod * m_ullCPUFrequency;
                else
                    m_ullNextACKTime = currtime + m_ullACKInt;

                m_iPktCount = 0;
                m_iLightACKCount = 1;
            }
            else if (m_iSelfClockInterval * m_iLightACKCount <= m_iPktCount)
            {
                //send a "light" ACK
                sendCtrl(2, null, null, 4);
                ++m_iLightACKCount;
            }

            // we are not sending back repeated NAK anymore and rely on the sender's EXP for retransmission
            //if ((m_pRcvLossList.getLossLength() > 0) && (currtime > m_ullNextNAKTime))
            //{
            //   // NAK timer expired, and there is loss to be reported.
            //   sendCtrl(3);
            //
            //   CTimer.rdtsc(currtime);
            //   m_ullNextNAKTime = currtime + m_ullNAKInt;
            //}

            ulong next_exp_time;
            if (m_pCC.m_bUserDefinedRTO)
                next_exp_time = m_ullLastRspTime + (ulong)m_pCC.m_iRTO * m_ullCPUFrequency;
            else
            {
                ulong exp_int = (ulong)(m_iEXPCount * (m_iRTT + 4 * m_iRTTVar) + m_iSYNInterval) * m_ullCPUFrequency;
                if (exp_int < (ulong)m_iEXPCount * m_ullMinExpInt)
                    exp_int = (ulong)m_iEXPCount * m_ullMinExpInt;
                next_exp_time = m_ullLastRspTime + exp_int;
            }

            if (currtime > next_exp_time)
            {
                // Haven't receive any information from the peer, is it dead?!
                // timeout: at least 16 expirations and must be greater than 10 seconds
                if ((m_iEXPCount > 16) && (currtime - m_ullLastRspTime > 5000000 * m_ullCPUFrequency))
                {
                    //
                    // Connection is broken. 
                    // UDT does not signal any information about this instead of to stop quietly.
                    // Application will detect this when it calls any UDT methods next time.
                    //
                    m_bClosing = true;
                    m_bBroken = true;
                    m_iBrokenCounter = 30;

                    // update snd U list to remove this socket
                    m_pSndQueue.m_pSndUList.update(this);

                    releaseSynch();

                    // app can call any UDT API to learn the connection_broken error
                    //s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN | UDT_EPOLL_OUT | UDT_EPOLL_ERR, true);

                    Timer.triggerEvent();

                    return;
                }

                // sender: Insert all the packets sent after last received acknowledgement into the sender loss list.
                // recver: Send out a keep-alive packet
                if (m_pSndBuffer.getCurrBufSize() > 0)
                {
                    if ((SequenceNumber.incseq(m_iSndCurrSeqNo) != m_iSndLastAck) && (m_pSndLossList.getLossLength() == 0))
                    {
                        // resend all unacknowledged packets on timeout, but only if there is no packet in the loss list
                        int csn = m_iSndCurrSeqNo;
                        int num = m_pSndLossList.insert(m_iSndLastAck, csn);
                        m_iTraceSndLoss += num;
                        m_iSndLossTotal += num;
                    }

                    m_pCC.onTimeout();
                    CCUpdate();

                    // immediately restart transmission
                    m_pSndQueue.m_pSndUList.update(this);
                }
                else
                {
                    sendCtrl(1);
                }

                ++m_iEXPCount;
                // Reset last response time since we just sent a heart-beat.
                m_ullLastRspTime = currtime;
            }
        }
    }
}