/*H*************************************************************************************************/
/*!

    \File    commudp.c

    \Description
        This is a reliable UDP transport class optimized for use in a
        controller passing game applications. When the actual data
        bandwidth is low (as is typical with controller passing), it
        sends redundant data in order to quickly recover from any lost
        packets. Overhead is low adding only 8 bytes per packet in
        addition to UDP/IP overhead. This module support mutual
        connects in order to allow connections through firewalls.

    \Copyright
        Copyright (c) Tiburon Entertainment / Electronic Arts 1999-2003.  ALL RIGHTS RESERVED.

    \Version    0.1        02/09/99 (GWS) First Version
    \Version    0.2        02/14/99 (GWS) Revised and enhanced
    \Version    0.5        02/14/99 (GWS) Alpha release
    \Version    1.0        07/30/99 (GWS) Final release
    \Version    2.0        10/27/99 (GWS) Revised to use winsock 1.1/2.0
    \Version    2.1        12/04/99 (GWS) Removed winsock 1.1 support
    \Version    2.2        01/12/00 (GWS) Fixed receive tick bug
    \Version    2.3        06/12/00 (GWS) Added fastack for low-latency nets
    \Version    2.4        12/04/00 (GWS) Added firewall penetrator
    \Version    3.0        12/04/00 (GWS) Reported to dirtysock
    \Version    3.1        11/20/02 (JLB) Added Send() flags parameter
    \Version    3.2        02/18/03 (JLB) Fixes for multiple connection support
    \Version    3.3        05/06/03 (GWS) Allowed poke to come from any IP
    \Version    3.4        09/02/03 (JLB) Added unreliable packet type
    \Version    4.0        09/12/03 (JLB) Per-send optional unreliable transport
    \Version    5.0        07/07/09 (jrainy) Putting meta-data bits over the high bits of the sequence number
*/
/*************************************************************************************************H*/


/*** Include files *********************************************************************/

#include <stdio.h>
#include <string.h>

#include "DirtySDK/dirtysock.h"
#include "DirtySDK/dirtysock/dirtymem.h"
#include "DirtySDK/comm/commall.h"
#include "DirtySDK/comm/commudp.h"
#include "DirtySDK/comm/commudputil.h"

/*** Defines ***************************************************************************/

#undef COMM_PRINT
#define COMM_PRINT      (0)

#if defined(DIRTYCODE_PC)
    #define ESC_CAUSES_LOSS (DIRTYCODE_DEBUG && 0)
#else
    #define ESC_CAUSES_LOSS (0)
#endif

#if ESC_CAUSES_LOSS
#include <windows.h>
#endif

#define BUSY_KEEPALIVE  (100)
#define IDLE_KEEPALIVE  (2500)
#define PENETRATE_RATE  (1000)
#define UNACK_LIMIT     (2048)

//! max additional space needed by a commudp meta type
#define COMMUDP_MAX_METALEN (8)

#define REDUNDANT_LIMIT_DEFAULT (64)

#define COMMUDP_VERSION_1_0     (0x0100)
#define COMMUDP_VERSION_1_1     (0x0101)
#define COMMUDP_VERSION         (COMMUDP_VERSION_1_1)

/*** Macros ****************************************************************************/

/*** Type Definitions ******************************************************************/

//! raw protocol packet format
typedef struct
{
    //! packet header which is not sent/received (this data is used internally)
    struct {
        int32_t iLen;                       //!< variable data len (-1=none) (used int32_t for alignment)
        uint32_t uWhen;                     //!< tick when a packet was received
        uint32_t uMeta;                     //!< four-bit metadata field extracted from seq field

// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
#ifdef ENABLED_LATENCY_METER
		uint64_t aLatency[IN_PHASE_NUM];
		uint64_t *pLatency;
#endif
// FIFA_END
    } head;
    //! packet body which is sent/received
    struct {
        uint32_t uSeq;                      //!< packet type or sequence number
        uint32_t uAck;                      //!< acknowledgement of last packet
        uint8_t  aData[SOCKET_MAXUDPRECV-8];//!< user data
    } body;
} RawUDPPacketT;

//! raw protocol packet header -- used for stack local formatting of handshake packets, and packets with no data
typedef struct
{
    //! packet header which is not sent/received (this data is used internally)
    struct {
        int32_t iLen;           //!< variable data len (-1=none) (used int32_t for alignment)
        uint32_t uWhen;         //!< tick when a packet was received
        uint32_t uMeta;         //!< four-bit metadata field extracted from seq field

// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
#ifdef ENABLED_LATENCY_METER
		uint64_t aLatency[IN_PHASE_NUM];
		uint64_t *pLatency;
#endif
// FIFA_END
    } head;
    //! packet body which is sent/received
    struct {
        uint32_t uSeq;          //!< packet type or seqeunce number
        uint32_t uAck;          //!< acknowledgement of last packet
        uint32_t uCid;          //!< client id (v1.0: in INIT/POKE packets; v1.1+: in INIT/POKE/CONN packets)
        uint8_t  aVers[2];      //!< protocol version (v1.1+ only)
        uint8_t  aData[62];     //!< space for possible metadata included in control packets
    } body;
} RawUDPPacketHeadT;

//! private module storage
struct CommUDPRef
{
    //! common header is first
    CommRef Common;

    //! max amount of unacknowledged data that can be sent in one go (default 2k)
    uint32_t uUnackLimit;

    //! max amount of data that can be sent redundantly (default = REDUNDANT_LIMIT_DEFAULT)
    uint32_t uRedundantLimit;

    //! linked list of all instances
    CommUDPRef *pLink;
    //! comm socket
    SocketT *pSocket;
    //! peer address
    struct sockaddr PeerAddr;

    //! port state
    enum {
        DEAD,       //!< dead
        IDLE,       //!< idle
        CONN,       //!< conn
        LIST,       //!< list
        OPEN,       //!< open
        CLOSE       //!< close
    } eState;

    //! identifier to keep from getting spoofed
    uint32_t uConnIdent;

    //! type of metachunk to include in stream (zero=none)
    uint32_t uMetaType;

    //! protocol version
    uint16_t uVers;
    uint16_t _pad;
    
    //! unique client identifier (used for game server identification)
    uint32_t uClientIdent;
    //! remote client identifier
    uint32_t uRemClientIdent;

    //! width of receive records (same as width of send)
    int32_t iRcvWid;
    //! length of receive buffer (multiple of rcvwid)
    int32_t iRcvLen;
    //! fifo input offset
    int32_t iRcvInp;
    //! fifo output offset
    int32_t iRcvOut;
    //! pointer to buffer storage
    char *pRcvBuf;
    //! next packet expected (sequence number)
    uint32_t uRcvSeq;
    //! next unreliable packet expected
    uint32_t uUnreliableRcvSeq;
    //! last packet we acknowledged
    uint32_t uRcvAck;
    //! number of unacknowledged received bytes
    int32_t iRcvUnack;

    //! width of send record (same as width of receive)
    int32_t iSndWid;
    //! length of send buffer (multiple of sndwid)
    int32_t iSndLen;
    //! fifo input offset
    int32_t iSndInp;
    //! fifo output offset
    int32_t iSndOut;
    //! current output point within fifo
    int32_t iSndNxt;
    //! pointer to buffer storage
    char *pSndBuf;
    //! next packet to send (sequence number)
    uint32_t uSndSeq;
    //! unreliable packet sequence number
    uint32_t uUnreliableSndSeq;
    //! last send result
    uint32_t uSndErr;

    //! tick at which last packet was sent
    uint32_t uSendTick;
    //! tick at which last reliable packet was sent (used for resend tracking)
    uint32_t uSendReliableTick;
    //! tick at which last packet was received
    uint32_t uRecvTick;
    //! tick at which last idle callback made
    uint32_t uIdleTick;

    //! control access during callbacks
    volatile int32_t iCallback;
    //! indicate there is an event pending
    uint32_t uGotEvent;
    //! callback routine pointer
    void (*pCallProc)(void *pRef, int32_t iEvent);
};

/*** Function Prototypes ***************************************************************/

/*** Variables *************************************************************************/

// Private variables

//! linked list of port objects
static CommUDPRef   *g_link = NULL;

//! semaphore to synchronize thread access
static NetCritT     g_crit;

//! missed event marker
static int32_t      g_missed;

//! variable indicates call to _CommUDPEvent() in progress
static int32_t      g_inevent;

#if DIRTYCODE_LOGGING
static const char  *g_strConnNames[] = { "COMMUDP_RAW_PACKET_INVALID", "COMMUDP_RAW_PACKET_INIT", "COMMUDP_RAW_PACKET_CONN", "COMMUDP_RAW_PACKET_DISC", "COMMUDP_RAW_PACKET_NAK", "COMMUDP_RAW_PACKET_POKE" };
#endif

// Public variables


/*** Private Functions *****************************************************************/

/*F*************************************************************************************************/
/*!
    \Function    _CommUDPSeqnDelta

    \Description
        Compute the sequence number off of uPos by iDelta places
        Can NOT be used for unreliable sequence offsetting

    \Input uPos     - starting position
    \Input iDelta   - offset

    \Output
        uint32_t    - resulting position

    \Version 07/07/09 (jrainy)
*/
/*************************************************************************************************F*/
static uint32_t _CommUDPSeqnDelta(uint32_t uPos, int32_t iDelta)
{
    return(((uPos + iDelta + COMMUDP_RAW_PACKET_DATA_WINDOW - COMMUDP_RAW_PACKET_DATA) % COMMUDP_RAW_PACKET_DATA_WINDOW) + COMMUDP_RAW_PACKET_DATA);
}

/*F*************************************************************************************************/
/*!
    \Function    _CommUDPSeqnDiff

    \Description
        Compute the difference in position between two sequence numbers
        Can NOT be used to compute unreliable sequence differences

    \Input uPos1    - source position
    \Input uPos2    - target position

    \Output
        int32_t     - signed difference in position

    \Version 07/07/09 (jrainy)
*/
/*************************************************************************************************F*/
static int32_t _CommUDPSeqnDiff(uint32_t uPos1, uint32_t uPos2)
{
    return((((uPos1 - uPos2) + (3 * COMMUDP_RAW_PACKET_DATA_WINDOW / 2)) % COMMUDP_RAW_PACKET_DATA_WINDOW) - (COMMUDP_RAW_PACKET_DATA_WINDOW / 2));
}

/*F*************************************************************************************************/
/*!
    \Function    _CommUDPSetAddrInfo

    \Description
        Sets peer/host addr/port info in common ref.

    \Input *pRef    - reference pointer
    \Input *pSin    - address pointer

    \Version 04/16/04 (JLB)
*/
/*************************************************************************************************F*/
static void _CommUDPSetAddrInfo(CommUDPRef *pRef, struct sockaddr *pSin)
{
    // save peer addr/port info in common ref
    pRef->Common.peerip = SockaddrInGetAddr(pSin);
    pRef->Common.peerport = SockaddrInGetPort(pSin);
    NetPrintf(("commudp: [%p] peer=%a:%d\n", pRef, pRef->Common.peerip, pRef->Common.peerport));
}

/*F*************************************************************************************************/
/*!
    \Function    _CommUDPSetSocket

    \Description
        Sets socket in ref and socketref in common portion of ref.

    \Input *pRef    - reference pointer
    \Input *pSocket - socket to set

    \Version 08/24/04 (JLB)
*/
/*************************************************************************************************F*/
static void _CommUDPSetSocket(CommUDPRef *pRef, SocketT *pSocket)
{
    pRef->pSocket = pSocket;
    pRef->Common.sockptr = pSocket;
    if (pSocket != NULL)
    {
        struct sockaddr SockAddr;
        // save host addr/port info in common ref
        SocketInfo(pRef->pSocket, 'bind', 0, &SockAddr, sizeof(SockAddr));
        pRef->Common.hostip = SocketGetLocalAddr();
        pRef->Common.hostport = SockaddrInGetPort(&SockAddr);
        NetPrintf(("commudp: [%p] host=%a:%d\n", pRef, pRef->Common.hostip, pRef->Common.hostport));
    }
    else
    {
        pRef->Common.hostip = 0;
        pRef->Common.hostport = 0;
    }
}

/*F*************************************************************************************************/
/*!
    \Function    _CommUDPSetConnID

    \Description
        Sets connident to the 32bit hash of the specified connection identifier string, if any.

    \Input *pRef        - reference pointer
    \Input *pStrConn    - pointer to user-specified connection string

    \Version 06/16/04 (JLB)
*/
/*************************************************************************************************F*/
static void _CommUDPSetConnID(CommUDPRef *pRef, const char *pStrConn)
{
    const char *pConnID = strchr(pStrConn, '#');
    if (pConnID != NULL)
    {
        pRef->uConnIdent = NetHash(pConnID+1);
    }
}

/*F*************************************************************************************************/
/*!
    \Function    _CommUDPResetTransfer

    \Description
        Reset the transfer state

    \Input *pRef    - reference pointer

    \Version 12/04/00 (GWS)
*/
/*************************************************************************************************F*/
static void _CommUDPResetTransfer(CommUDPRef *pRef)
{
    // reset packet received bool
    pRef->Common.bpackrcvd = FALSE;
    // reset the send queue
    pRef->iSndInp = 0;
    pRef->iSndOut = 0;
    pRef->iSndNxt = 0;
    // reset the sequence number
    pRef->uSndSeq = COMMUDP_RAW_PACKET_DATA;
    pRef->uUnreliableSndSeq = COMMUDP_RAW_PACKET_UNREL;
    // reset the receive queue
    pRef->iRcvInp = 0;
    pRef->iRcvOut = 0;
    // reset the packet sequence number
    pRef->uRcvSeq = COMMUDP_RAW_PACKET_DATA;
    pRef->uUnreliableRcvSeq = COMMUDP_RAW_PACKET_UNREL;

    // no unack data
    pRef->iRcvUnack = 0;

    // make sendtick really old (in protocol terms)
    pRef->uSendTick = pRef->uSendReliableTick = NetTick()-5000;
    // recvtick must be older than tick because the first
    // packet that arrives may come in moments before this
    // initialization takes place and without this adjustment
    // code can compute an elapsed receive time of 0xffffffff]
    pRef->uRecvTick = NetTick()-5000;
}

/*F*************************************************************************************************/
/*!
    \Function    _CommUDPOverhead

    \Description
        Computes the bandwidth overhead associated with a packet of length packetLength

    \Input *pRef        - module reference
    \Input iPktLen      - length of the packet we are about to send

    \Output
        int32_t         - the associated overhead. 28 on most platforms, but higher on xbox360.

    \Version 01/08/07 (JRainy)

*/
/*************************************************************************************************F*/
static int32_t _CommUDPOverhead(CommUDPRef *pRef, int32_t iPktLen)
{
    // start with basic IP+UDP header size
    int32_t iOverhead = 28;
    return(iOverhead);
}

/*F*************************************************************************************************/
/*!
    \Function    _CommUDPWrite

    \Description
        Send a packet to the peer

    \Input *pRef        - reference pointer
    \Input *pPacket     - packet pointer
    \Input *pPeerAddr   - address of peer to send to
    \Input uCurrTick    - current tick

    \Output
        int32_t         - negative=error, zero=ok

    \Version 12/04/00 (GWS)

*/
/*************************************************************************************************F*/
static int32_t _CommUDPWrite(CommUDPRef *pRef, RawUDPPacketT *pPacket, struct sockaddr *pPeerAddr, uint32_t uCurrTick)
{
    int32_t iErr;
    int32_t iLen;

    // figure full packet length (nak/ack fields + variable data)
    iLen = sizeof(pPacket->body)-sizeof(pPacket->body.aData)+pPacket->head.iLen;

    // fill in metatype info    
    if (pRef->uMetaType == 1)
    {
        int32_t iMetaOffset = ((pPacket->body.uSeq == COMMUDP_RAW_PACKET_INIT) || (pPacket->body.uSeq == COMMUDP_RAW_PACKET_POKE)) ? 4 : 0;
        // make room for metadata
        memmove(&pPacket->body.aData[COMMUDP_RAW_METATYPE1_SIZE+iMetaOffset], &pPacket->body.aData[0+iMetaOffset], iLen-iMetaOffset);
        iLen += COMMUDP_RAW_METATYPE1_SIZE;
        // metatype1 src clientident
        pPacket->body.aData[0+iMetaOffset] = (uint8_t)(pRef->uClientIdent >> 24);
        pPacket->body.aData[1+iMetaOffset] = (uint8_t)(pRef->uClientIdent >> 16);
        pPacket->body.aData[2+iMetaOffset] = (uint8_t)(pRef->uClientIdent >> 8);
        pPacket->body.aData[3+iMetaOffset] = (uint8_t)(pRef->uClientIdent);
        // metatype1 dst clientident
        pPacket->body.aData[4+iMetaOffset] = (uint8_t)(pRef->uRemClientIdent >> 24);
        pPacket->body.aData[5+iMetaOffset] = (uint8_t)(pRef->uRemClientIdent >> 16);
        pPacket->body.aData[6+iMetaOffset] = (uint8_t)(pRef->uRemClientIdent >> 8);
        pPacket->body.aData[7+iMetaOffset] = (uint8_t)(pRef->uRemClientIdent);
        // set metatype header
        pPacket->body.uSeq |= (pRef->uMetaType & 0xf) << COMMUDP_SEQ_META_SHIFT;
    }

    #if COMM_PRINT > 1
    NetPrintf(("commudp: [%p] seq:0x%08x ack:0x%08x  send %d bytes to %a:%d\n", pRef, pPacket->body.uSeq, pPacket->body.uAck, iLen, SockaddrInGetAddr(pPeerAddr), SockaddrInGetPort(pPeerAddr)));
    #endif
    #if COMM_PRINT > 2
    NetPrintMem(&pPacket->body, iLen, "cudp-send");
    #endif

    // translate seq and ack to network order for send
    pPacket->body.uSeq = SocketHtonl(pPacket->body.uSeq);
    pPacket->body.uAck = SocketHtonl(pPacket->body.uAck);

    // store send time in misc field of sockaddr
    SockaddrInSetMisc(pPeerAddr, uCurrTick);

    #if ESC_CAUSES_LOSS
    // lose packets when escape is pressed
    if (GetAsyncKeyState(VK_ESCAPE) < 0)
    {
        NetPrintf(("commudp: [%p] dropping packet to simulate packet loss (seq=0x%08x)\n", pRef, SocketNtohl(pPacket->body.uSeq)));
        iErr = iLen;
    }
    else
    {
        // send the packet
        iErr = SocketSendto(pRef->pSocket, (char *)&pPacket->body, iLen, 0, pPeerAddr, sizeof(*pPeerAddr));
    }
    #else
    // send the packet
    iErr = SocketSendto(pRef->pSocket, (char *)&pPacket->body, iLen, 0, pPeerAddr, sizeof(*pPeerAddr));
    #endif


    // translate seq and ack back to host order
    pPacket->body.uSeq = SocketNtohl(pPacket->body.uSeq);
    pPacket->body.uAck = SocketNtohl(pPacket->body.uAck);

    // check for success
    if (iErr == iLen)
    {
        // update last send time
        pRef->uSendTick = uCurrTick;

        // do stats
        if (pRef->eState == OPEN)
        {
            pRef->Common.datasent += iLen;
            pRef->Common.packsent += 1;
        }

        pRef->Common.overhead += _CommUDPOverhead(pRef, iLen);

        // is the packet reliable?
        if ((pPacket->body.uSeq & COMMUDP_SEQ_MASK) >= COMMUDP_RAW_PACKET_DATA)
        {
            // we assume any reliable send includes an up to date ack value
            // which means that we can reset the unacked data count to zero
            pRef->iRcvUnack = 0;
            // update reliable send timer
            pRef->uSendReliableTick = uCurrTick;
        }
    }
    else
    {
        NetPrintf(("commudp: [%p] SocketSendto() returned %d\n", pRef, iErr));
        pRef->uSndErr = iErr;
        iErr = -1;
    }

    return(iErr);
}

/*F*************************************************************************************************/
/*!
    \Function    _CommUDPClose

    \Description
        Close the connection

    \Input *pRef    - reference pointer
    \Input uCurrTick- current tick

    \Output
        int32_t     - negative=error, zero=ok

    \Version 12/04/00 (GWS)

*/
/*************************************************************************************************F*/
static int32_t _CommUDPClose(CommUDPRef *pRef, uint32_t uCurrTick)
{
    RawUDPPacketHeadT Packet;

    // if we're open, shut down connection
    if (pRef->eState == OPEN)
    {
        // see if any output data pending
        if (pRef->iSndNxt != pRef->iSndInp)
        {
            NetPrintf(("commudp: [%p] unsent data pending\n", pRef));
        }
        else if (pRef->iSndOut != pRef->iSndInp)
        {
            NetPrintf(("commudp: [%p] unacked data pending\n", pRef));
        }

        // send a disconnect message
        Packet.head.iLen = 0;
        Packet.body.uSeq = COMMUDP_RAW_PACKET_DISC;
        Packet.body.uAck = pRef->uConnIdent;
        _CommUDPWrite(pRef, (RawUDPPacketT *)&Packet, &pRef->PeerAddr, uCurrTick);
    }

    // set to disconnect state
    NetPrintf(("commudp: [%p] closed connection\n", pRef));
    pRef->uConnIdent = 0;
    pRef->eState = CLOSE;
    return(0);
}

/*F*************************************************************************************************/
/*!
    \Function    _CommUDPFormatHandshake

    \Description
        Format handshake packet

    \Input *pRef    - reference pointer
    \Input *pPacket - [out] buffer to format handshake packet into
    \Input uKind    - packet kind (COMMUDP_RAW_PACKET_INIT, COMMUDP_RAW_PACKET_POKE, COMMUDP_RAW_PACKET_CONN)

    \Version 10/29/2015 (jbrookes)
*/
/*************************************************************************************************F*/
static void _CommUDPFormatHandshake(CommUDPRef *pRef, RawUDPPacketHeadT *pPacket, uint32_t uKind)
{
    pPacket->body.uSeq = uKind;
    pPacket->body.uAck = pRef->uConnIdent;
    #if COMMUDP_VERSION > COMMUDP_VERSION_1_0
    pPacket->body.uCid = SocketHtonl(pRef->uClientIdent);
    pPacket->body.aVers[0] = COMMUDP_VERSION>>8;
    pPacket->body.aVers[1] = COMMUDP_VERSION&0x0f;
    pPacket->head.iLen = 6;
    #else
    if ((kind == COMMUDP_RAW_PACKET_INIT) || (kind == COMMUDP_RAW_PACKET_POKE))
    {
        pPacket->body.cid = SocketHtonl(pRef->clientident);
        pPacket->head.iLen = 4;
    }
    else
    {
        pPacket->head.iLen = 0;
    }
    #endif
}

/*F*************************************************************************************************/
/*!
    \Function    _CommUDPProcessSetup

    \Description
        Process a setup/teardown request

    \Input *pRef    - reference pointer
    \Input *pPacket - requesting packet
    \Input *pSin    - address
    \Input uCurrTick- current tick

    \Version 12/04/00 (GWS)
*/
/*************************************************************************************************F*/
static void _CommUDPProcessSetup(CommUDPRef *pRef, const RawUDPPacketT *pPacket, struct sockaddr *pSin, uint32_t uCurrTick)
{
    // make sure the session identifier matches
    if (pPacket->body.uAck != pRef->uConnIdent)
    {
        NetPrintf(("commudp: [%p] warning - connident mismatch (expected=0x%08x got=0x%08x)\n", pRef, pRef->uConnIdent, pPacket->body.uAck));
        // an init packet with a different session identifier
        // indicates that the old session has closed
        if (pPacket->body.uSeq == COMMUDP_RAW_PACKET_INIT)
        {
            pRef->eState = CLOSE;
        }
        return;
    }
    
    // update valid receive time -- must put into past to avoid race condition
    pRef->uRecvTick = uCurrTick-1000;

    // version identification
    #if COMMUDP_VERSION > COMMUDP_VERSION_1_0
    if ((pRef->uVers == 0) && ((pPacket->body.uSeq == COMMUDP_RAW_PACKET_INIT) || (pPacket->body.uSeq == COMMUDP_RAW_PACKET_CONN) || (pPacket->body.uSeq == COMMUDP_RAW_PACKET_POKE)))
    {
        RawUDPPacketHeadT *hshk = (RawUDPPacketHeadT *)pPacket;
        pRef->uVers = (pPacket->head.iLen > 4) ? (hshk->body.aVers[0] << 8) | hshk->body.aVers[1] : COMMUDP_VERSION_1_0;
        NetPrintf(("commudp: [%p] vers=%d.%d\n", pRef, pRef->uVers >> 8, pRef->uVers & 0xff));
    }
    #else
    pRef->uVers = COMMUDP_VERSION_1_0;
    #endif

    // response to connection/poke query
    if ((pPacket->body.uSeq == COMMUDP_RAW_PACKET_INIT) || (pPacket->body.uSeq == COMMUDP_RAW_PACKET_POKE))
    {
        RawUDPPacketHeadT connpacket;
        
        // set host/peer addr/port info
        _CommUDPSetAddrInfo(pRef, pSin);

        // send CONN in response to INIT/POKE
        NetPrintf(("commudp: [%p] sending CONN packet to %a:%d connident=0x%08x\n", pRef, SockaddrInGetAddr(&pRef->PeerAddr),
            SockaddrInGetPort(&pRef->PeerAddr), pRef->uConnIdent));
        _CommUDPFormatHandshake(pRef, &connpacket, COMMUDP_RAW_PACKET_CONN);
        _CommUDPWrite(pRef, (RawUDPPacketT *)&connpacket, &pRef->PeerAddr, uCurrTick);
        return;
    }

    // response to a connect confirmation
    if (pPacket->body.uSeq == COMMUDP_RAW_PACKET_CONN)
    {
        // change to open if not already there
        if (pRef->eState == CONN)
        {
            // set host/peer addr/port info
            _CommUDPSetAddrInfo(pRef, pSin);
            NetPrintf(("commudp: [%p] transitioning to OPEN state due to received COMMUDP_RAW_PACKET_CONN\n", pRef));
            pRef->eState = OPEN;
        }
        return;
    }

    // response to disconnect message
    if (pPacket->body.uSeq == COMMUDP_RAW_PACKET_DISC)
    {
        // close the connection
        if (pRef->eState == OPEN)
        {
            pRef->eState = CLOSE;
        }
        NetPrintf(("commudp: [%p] received DISC packet\n", pRef));
    }

    // should not get here
}

/*F*************************************************************************************************/
/*!
    \Function    _CommUDPProcessInit

    \Description
        Initiate a connection

    \Input *pRef    - reference pointer
    \Input uCurrTick- current tick

    \Version 12/04/00 (GWS)

*/
/*************************************************************************************************F*/
static void _CommUDPProcessInit(CommUDPRef *pRef, uint32_t uCurrTick)
{
    RawUDPPacketHeadT Packet;

    // send INIT to peer
    NetPrintf(("commudp: [%p] sending INIT packet to %a:%d connident=0x%08x clientident=0x%08x\n", pRef, SockaddrInGetAddr(&pRef->PeerAddr),
        SockaddrInGetPort(&pRef->PeerAddr), pRef->uConnIdent, pRef->uClientIdent));
    _CommUDPFormatHandshake(pRef, &Packet, COMMUDP_RAW_PACKET_INIT);
    _CommUDPWrite(pRef, (RawUDPPacketT *)&Packet, &pRef->PeerAddr, uCurrTick);
}

// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
#ifdef ENABLED_LATENCY_METER
uint32_t SetTimestamp(RawUDPPacketT *packet, uint32_t timecnt, uint64_t **timebuf, uint32_t timenum)
{
	if (packet->head.pLatency != NULL)
	{
		uint64_t time = NetProcTime();
		if (packet->head.pLatency[OUT_COMMUDP_SEND])
		{
			packet->head.pLatency[OUT_COMMUDP_RETRY_COUNT]++;
			packet->head.pLatency[OUT_COMMUDP_RETRY_TIME] = time;
		}
		else
		{
			packet->head.pLatency[OUT_COMMUDP_SEND] = time;
		}
		if (timecnt < timenum)
		{
			timebuf[timecnt++] = packet->head.pLatency;
		}
	}
	return timecnt;
}
#endif
// FIFA_END

/*F*************************************************************************************************/
/*!
    \Function    _CommUDPProcessOutput

    \Description
        Send data packet(s)

    \Input *pRef    - reference pointer
    \Input uCurrTick- current tick

    \Version 12/04/00 (GWS)
*/
/*************************************************************************************************F*/
static void _CommUDPProcessOutput(CommUDPRef *pRef, uint32_t uCurrTick)
{
    int32_t iIndex, iCount;
    int32_t iTLimit = pRef->uUnackLimit, iPLimit;
    static RawUDPPacketT Multi;
    RawUDPPacketT *pBuffer;
    static uint32_t uMLimit = 0x20000000;
    int32_t iMetaLen = CommUDPUtilGetMetaSize(pRef->uMetaType);
    int32_t iSubpacketLimit = (pRef->uVers > COMMUDP_VERSION_1_0) ? 1530 : 250;

// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
#ifdef ENABLED_LATENCY_METER
	uint64_t *	timebuf[32];
	uint32_t	timeidx = 0;
	uint32_t	timenum = sizeof(timebuf) / sizeof(timebuf[0]);
#endif
// FIFA_END

    // figure unacked data length
    for (iIndex = pRef->iSndOut; iIndex != pRef->iSndNxt; iIndex = (iIndex+pRef->iSndWid)%pRef->iSndLen) {
        pBuffer = (RawUDPPacketT *) &pRef->pSndBuf[iIndex];
        // count down the limit
        iTLimit -= pBuffer->head.iLen;
    }

    // allow a minimum send rate (256 bytes per 250 ms)
    if ((iTLimit < 256) && (uCurrTick-pRef->uSendTick > 250))
        iTLimit = 256;

    // send as much data as limit allows
    while (iTLimit > 0) {

        // limit size of forward packet consolidation
        iPLimit = SocketInfo(NULL, 'maxp', 0, NULL, 0) - iMetaLen - sizeof(Multi.body) + sizeof(Multi.body.aData);
        
        if (iPLimit > iTLimit)
            iPLimit = iTLimit;

        // attempt forward consolidation of packets
        for (iCount = 0; (iCount < 8) && (iPLimit > 0) && (pRef->iSndNxt != pRef->iSndInp); ++iCount) {
            pBuffer = (RawUDPPacketT *) &pRef->pSndBuf[pRef->iSndNxt];
            iPLimit -= pBuffer->head.iLen;
            iPLimit -= CommUDPUtilEncodeSubpacketSize(NULL, pBuffer->head.iLen);
            // if not the first packet, then we must be careful about size
            if ((iCount > 0) && (iPLimit <= 0))
                break;
            pRef->iSndNxt = (pRef->iSndNxt+pRef->iSndWid) % pRef->iSndLen;
            // if packet is too large for subpacket encoding, it must be final packet (first in multisend)
            if (pBuffer->head.iLen > iSubpacketLimit) {
                ++iCount;
                break;
            }
        }
        if (iCount == 0)
            return;

        // setup main packet
        iIndex = (pRef->iSndNxt+pRef->iSndLen-pRef->iSndWid)%pRef->iSndLen;
        pBuffer = (RawUDPPacketT *) &pRef->pSndBuf[iIndex];
        // if they want a callback, do it now
        if (pRef->Common.SendCallback != NULL)
            pRef->Common.SendCallback((CommRef *)pRef, pBuffer->body.aData, pBuffer->head.iLen, uCurrTick);
        ds_memcpy(&Multi, pBuffer, sizeof(pBuffer->head)+8+pBuffer->head.iLen);
        iCount -= 1;

// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
#ifdef ENABLED_LATENCY_METER
		timeidx = SetTimestamp(pBuffer, timeidx, timebuf, timenum);
#endif
// FIFA_END

        // add in required preceding packets
        for (; iCount > 0; --iCount) {
            // move to preceding packet
            iIndex = (iIndex+pRef->iSndLen-pRef->iSndWid)%pRef->iSndLen;
            // point to potential piggyback packet
            pBuffer = (RawUDPPacketT *) &pRef->pSndBuf[iIndex];
            // if they want a callback, do it now
            if (pRef->Common.SendCallback != NULL)
                pRef->Common.SendCallback((CommRef *)pRef, pBuffer->body.aData, pBuffer->head.iLen, uCurrTick);
            // combine the packets
            Multi.body.uSeq += COMMUDP_SEQ_MULTI_INC;
            ds_memcpy(Multi.body.aData+Multi.head.iLen, pBuffer->body.aData, pBuffer->head.iLen);
            Multi.head.iLen += pBuffer->head.iLen;
            Multi.head.iLen += CommUDPUtilEncodeSubpacketSize(Multi.body.aData+Multi.head.iLen, pBuffer->head.iLen);

// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
#ifdef ENABLED_LATENCY_METER
			timeidx = SetTimestamp(pBuffer, timeidx, timebuf, timenum);
#endif
// FIFA_END
        }

        // add in optional redundant packets
        while ((iIndex != pRef->iSndOut) && (Multi.body.uSeq <= uMLimit)) {
            // move to preceding packet
            iIndex = (iIndex+pRef->iSndLen-pRef->iSndWid)%pRef->iSndLen;
            // point to potential piggyback packet
            pBuffer = (RawUDPPacketT *) &pRef->pSndBuf[iIndex];
            // if packet is too large for subpacket encoding, we cannot multi-send it
            if (pBuffer->head.iLen > iSubpacketLimit)
                break;
            // see if combined length would be a problem
            if ((Multi.head.iLen + pBuffer->head.iLen + 1) > (signed)pRef->uRedundantLimit)
                break;
            // if they want a callback, do it now
            if (pRef->Common.SendCallback != NULL)
                pRef->Common.SendCallback((CommRef *)pRef, pBuffer->body.aData, pBuffer->head.iLen, uCurrTick);
            // combine the packets
            Multi.body.uSeq += COMMUDP_SEQ_MULTI_INC;
            ds_memcpy(Multi.body.aData+Multi.head.iLen, pBuffer->body.aData, pBuffer->head.iLen);
            Multi.head.iLen += pBuffer->head.iLen;
            Multi.head.iLen += CommUDPUtilEncodeSubpacketSize(Multi.body.aData+Multi.head.iLen, pBuffer->head.iLen);

// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
#ifdef ENABLED_LATENCY_METER
			timeidx = SetTimestamp(pBuffer, timeidx, timebuf, timenum);
#endif
// FIFA_END
        }

        // adjust the max redundancy
        if (iIndex == pRef->iSndOut) {
            uMLimit = 0x20000000;
        } else {
            uMLimit = (uMLimit < 0x80000000 ? uMLimit*2 : 0xf0000000);
        }

        // update the ack value (one less than one we are waiting for)
        pRef->uRcvAck = pRef->uRcvSeq;
        Multi.body.uAck = _CommUDPSeqnDelta(pRef->uRcvSeq, -1);
        // go ahead and send the packet
        if (_CommUDPWrite(pRef, &Multi, &pRef->PeerAddr, uCurrTick) < 0)
        {
// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
#ifdef ENABLED_LATENCY_METER
			for (uint32_t i = 0; i < timeidx; i++)
			{
				timebuf[i][OUT_COMMUDP_WRITE_FAIL]++;
			}
#endif
// FIFA_END

            return;
        }
        // count the bandwidth for this packet
        iTLimit -= Multi.head.iLen;
    }
}

/*F*************************************************************************************************/
/*!
    \Function    _CommUDPProcessFlow

    \Description
        Perform flow control based on ack/nak packets

    \Input *pRef    - reference pointer
    \Input *pPacket - incoming packet header
    \Input uCurrTick- current tick

    \Version 12/04/00 (GWS)

*/
/*************************************************************************************************F*/
static void _CommUDPProcessFlow(CommUDPRef *pRef, RawUDPPacketHeadT *pPacket, uint32_t uCurrTick)
{
    int32_t iNak;
    uint32_t uAck;

    // grab the ack point and nak flag
    iNak = (pPacket->body.uSeq == COMMUDP_RAW_PACKET_NAK);

    if (pPacket->body.uAck < COMMUDP_RAW_PACKET_DATA)
    {
        uAck = (iNak ? pPacket->body.uAck-1 : pPacket->body.uAck);
    }
    else
    {
        uAck = (iNak ? _CommUDPSeqnDelta(pPacket->body.uAck, -1) : pPacket->body.uAck);
    }

    // advance ack point
    while (pRef->iSndOut != pRef->iSndInp)
    {
        RawUDPPacketT *pBuffer = (RawUDPPacketT *) &pRef->pSndBuf[pRef->iSndOut];
        // see if this packet has been acked
        if (((uAck & COMMUDP_SEQ_MASK) < COMMUDP_RAW_PACKET_DATA) ||
            (_CommUDPSeqnDiff(uAck, pBuffer->body.uSeq) < 0))
        {
            break;
        }

        // if about to send this packet, skip to next
        if (pRef->iSndNxt == pRef->iSndOut)
            pRef->iSndNxt = (pRef->iSndNxt+pRef->iSndWid) % pRef->iSndLen;
        // remove the packet from the queue
        pRef->iSndOut = (pRef->iSndOut+pRef->iSndWid) % pRef->iSndLen;
    }

    // reset send point for nak
    if (iNak)
    {
        #if COMM_PRINT
        NetPrintf(("commudp: [%p] got NAK for packet %d\n", pRef, uAck+1));
        #endif
        // reset send point
        pRef->iSndNxt = pRef->iSndOut;
        // immediate restart
        _CommUDPProcessOutput(pRef, uCurrTick);
    }
}

/*F*************************************************************************************************/
/*!
    \Function    _CommUDPProcessInput

    \Description
        Process incoming data packet

    \Input *pRef    - module state
    \Input *pPacket - incoming packet header
    \Input *pData   - pointer to packet data
    \Input uCurrTick- current network tick

    \Output
        int32_t     - -1=nak, 0=old, 1=new, 2=buffer full

    \Version 12/04/2000 (gschaefer)
*/
/*************************************************************************************************F*/
static int32_t _CommUDPProcessInput(CommUDPRef *pRef, RawUDPPacketHeadT *pPacket, uint8_t *pData, uint32_t uCurrTick)
{
    RawUDPPacketT *pBuffer;

    // see if room in buffer for packet
    if ((pRef->iRcvInp+pRef->iRcvWid)%pRef->iRcvLen == pRef->iRcvOut)
    {
        // no room in buffer -- just drop packet
        // could nak, but would generate lots of
        // network activity with no result
        NetPrintf(("commudp: [%p] input buffer overflow\n", pRef));
        return(2);
    }

    // handle unreliable receive
    if (((pPacket->body.uSeq & COMMUDP_SEQ_MASK) >= COMMUDP_RAW_PACKET_UNREL) && ((pPacket->body.uSeq & COMMUDP_SEQ_MASK) < COMMUDP_RAW_PACKET_DATA))
    {
        // calculate the number of free packets in pRcvBuf
        int32_t queuepos = ((pRef->iRcvInp+pRef->iRcvLen)-pRef->iRcvOut)%pRef->iRcvLen;
        int32_t pktsfree = (pRef->iRcvLen - queuepos)/pRef->iRcvWid;

        // calculate delta between received sequence and expected sequence, accounting for wrapping
        int32_t delta = (pPacket->body.uSeq - pRef->uUnreliableRcvSeq) & (COMMUDP_RAW_PACKET_UNREL - 1);

        // update lost packet count
        if ((pPacket->body.uSeq > pRef->uUnreliableRcvSeq) || (delta < COMMUDP_RAW_PACKET_UNREL/4))
        {
            pRef->Common.packlost += delta;
        }

        // calculate new sequence number
        if ((pRef->uUnreliableRcvSeq = (pPacket->body.uSeq + 1)) >= COMMUDP_RAW_PACKET_DATA)
        {
            pRef->uUnreliableRcvSeq = COMMUDP_RAW_PACKET_UNREL + (pRef->uUnreliableRcvSeq - COMMUDP_RAW_PACKET_DATA);
        }

        // see if there is room to buffer (leave room for reliable packets)
        if (pktsfree <= 4)
        {
            return(2);
        }
    }
    else
    {
        // ignore old packets
        if (_CommUDPSeqnDiff(pPacket->body.uSeq, pRef->uRcvSeq) < 0)
        {
            return(0);
        }

        // immediate nak for missing packets
        if (_CommUDPSeqnDiff(pPacket->body.uSeq, pRef->uRcvSeq) > 0)
        {
            // update lost packet count
            pRef->Common.packlost += _CommUDPSeqnDiff(pPacket->body.uSeq, pRef->uRcvSeq);
            
            // send a nak packet
            #if COMM_PRINT
            NetPrintf(("commudp: [%p] sending a NAK of packet %d (tick=%u)\n", pRef, pRef->uRcvSeq, NetTick()));
            #endif
            pPacket->body.uSeq = COMMUDP_RAW_PACKET_NAK;
            pPacket->body.uAck = pRef->uRcvSeq;
            pPacket->head.iLen = 0;
            _CommUDPWrite(pRef, (RawUDPPacketT *)pPacket, &pRef->PeerAddr, uCurrTick);

            // update number of NAKs sent
            pRef->Common.naksent += 1;

            return(-1);
        }

        // no further processing for empty (ack) packets
        if (pPacket->head.iLen == 0)
        {
            return(0);
        }
    }

    // add the packet to the buffer
    pBuffer = (RawUDPPacketT *) &pRef->pRcvBuf[pRef->iRcvInp];
    // copy the packet
    ds_memcpy_s(pBuffer, sizeof(*pBuffer), pPacket, sizeof(*pPacket));
    ds_memcpy(pBuffer->body.aData, pData, pPacket->head.iLen);

// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
#ifdef ENABLED_LATENCY_METER
	pBuffer->head.aLatency[IN_SOCKET_QUEUE] = NetTickToProcTime(pPacket->head.uWhen);
	pBuffer->head.aLatency[IN_COMMUDP_QUEUE] = NetProcTime();
#endif
// FIFA_END

    // limit receive access for callbacks
    pRef->iCallback += 1;
    // add item to receive buffer
    pRef->iRcvInp = (pRef->iRcvInp+pRef->iRcvWid) % pRef->iRcvLen;

    // reliable specific processing
    if ((pPacket->body.uSeq & COMMUDP_SEQ_MASK) >= COMMUDP_RAW_PACKET_DATA)
    {
        pRef->uRcvSeq = _CommUDPSeqnDelta(pRef->uRcvSeq, 1);
        // add to unacknowledged byte count
        pRef->iRcvUnack += pBuffer->head.iLen;
    }

    // indicate we got an event
    pRef->uGotEvent |= 1;
    // let the callback process it
    if (pRef->Common.RecvCallback != NULL)
        pRef->Common.RecvCallback((CommRef *)pRef, pBuffer->body.aData, pBuffer->head.iLen, pBuffer->head.uWhen);
    // release access to receive
    pRef->iCallback -= 1;
    return(1);
}

/*F*************************************************************************************************/
/*!
    \Function    _CommUDPProcessPoke

    \Description
        Penetrate firewall with poke packet

    \Input *pRef    - reference pointer
    \Input uCurrTick- current tick

    \Version 07/07/00 (GWS)

*/
/*************************************************************************************************F*/
static void _CommUDPProcessPoke(CommUDPRef *pRef, uint32_t uCurrTick)
{
    RawUDPPacketHeadT Packet;

    // send POKE to peer
    NetPrintf(("commudp: [%p] sending POKE packet to %a:%d connident=0x%08x clientident=0x%08x\n", pRef, SockaddrInGetAddr(&pRef->PeerAddr),
        SockaddrInGetPort(&pRef->PeerAddr), pRef->uConnIdent, pRef->uClientIdent));
    _CommUDPFormatHandshake(pRef, &Packet, COMMUDP_RAW_PACKET_POKE);
    _CommUDPWrite(pRef, (RawUDPPacketT *)&Packet, &pRef->PeerAddr, uCurrTick);
}

/*F*************************************************************************************************/
/*!
    \Function    _CommUDPProcessAlive

    \Description
        Send a keepalive packet

    \Input *pRef    - reference pointer
    \Input uCurrTick- current tick

    \Version 12/04/00 (GWS)
*/
/*************************************************************************************************F*/
static void _CommUDPProcessAlive(CommUDPRef *pRef, uint32_t uCurrTick)
{
    RawUDPPacketHeadT Packet;

    // see if we should resend most recent packet
    if ((pRef->iSndOut != pRef->iSndInp) && (pRef->iSndNxt == pRef->iSndInp)) {
        // this shound result in a multi-send
        pRef->iSndNxt = (pRef->iSndNxt+pRef->iSndLen-pRef->iSndWid)%pRef->iSndLen;
        _CommUDPProcessOutput(pRef, uCurrTick);
        return;
    }

    // set our packet number
    Packet.body.uSeq = pRef->uSndSeq;
    // acknowledge packet prior to one we are waiting for
    pRef->uRcvAck = pRef->uRcvSeq;
    Packet.body.uAck = _CommUDPSeqnDelta(pRef->uRcvSeq, -1);

    // no data means keepalive
    Packet.head.iLen = 0;
    // send it
    #if COMM_PRINT
    NetPrintf(("commudp: [%p] sending keep-alive\n", pRef));
    #endif
    _CommUDPWrite(pRef, (RawUDPPacketT *)&Packet, &pRef->PeerAddr, uCurrTick);
}

/*F*************************************************************************************************/
/*!
    \Function    _CommUDPFlush

    \Description
        Flush output buffer

    \Input *pRef    - reference pointer
    \Input uLimit   - timeout in milliseconds
    \Input uCurrTick- current tick

    \Output
        uint32_t    - number of packets in buffer

    \Version 12/04/00 (GWS)

*/
/*************************************************************************************************F*/
static uint32_t _CommUDPFlush(CommUDPRef *pRef, uint32_t uLimit, uint32_t uCurrTick)
{
    int32_t iNumPackets;
    int32_t iIndex;
    RawUDPPacketT *pBuffer;

    iNumPackets = (((pRef->iSndInp+pRef->iSndLen)-pRef->iSndOut)%pRef->iSndLen)/pRef->iSndWid;
    NetPrintf(("commudp: [%p] flushing %d packets in send queue\n", pRef, iNumPackets));

    for (iIndex = pRef->iSndOut; iIndex != pRef->iSndNxt; iIndex = (iIndex+pRef->iSndWid)%pRef->iSndLen)
    {
        pBuffer = (RawUDPPacketT *) &pRef->pSndBuf[iIndex];
        _CommUDPWrite(pRef, pBuffer, &pRef->PeerAddr, uCurrTick);
    }

    return(iNumPackets);
}

/*F*************************************************************************************************/
/*!
    \Function    _CommUDPThreadData

    \Description
        Take care of data processing

    \Input uCurrTick- current tick

    \Output
        int32_t     - number of packets received

    \Version 12/04/00 (GWS)

*/
/*************************************************************************************************F*/
static int32_t _CommUDPThreadData(uint32_t uCurrTick)
{
    int32_t iLen;
    int32_t iCount = 0;
    CommUDPRef *pRef;
    CommUDPRef *pListen = NULL;
    struct sockaddr Sin;
    static RawUDPPacketT Packet;
    SocketT *pSocket;
    uint32_t bConnIdentMatch=0;
    uint32_t uLClientIdent=0, uRClientIdent=0;

    // init sockaddr (else unused bytes cause mismatch during ident)
    SockaddrInit(&Sin, AF_INET);

    // scan sockets for incoming packet
    Packet.head.iLen = -1;
    pSocket = NULL;
    for (pRef = g_link; pRef != NULL; pRef = pRef->pLink)
    {
        // make sure we need to scan this one
        if ((pRef->pSocket != NULL) && (pRef->pSocket != pSocket))
        {
            // see if data is pending
            pSocket = pRef->pSocket;
            iLen = sizeof(Sin);
            iLen = SocketRecvfrom(pSocket, (char *)&Packet.body, sizeof(Packet.body), 0, &Sin, &iLen);
            if (iLen > 0)
            {
                // track recieve overhead
                pRef->Common.rcvoverhead += sizeof(Packet.body)-sizeof(Packet.body.aData) + _CommUDPOverhead(pRef, iLen);
                pRef->Common.rcvoverhead += CommUDPUtilGetMetaSize(pRef->uMetaType);
                // get length and timestamp the packet
                Packet.head.iLen = iLen-(sizeof(Packet.body)-sizeof(Packet.body.aData));
                Packet.head.uWhen = SockaddrInGetMisc(&Sin);
                // translate seq and ack to host order
                Packet.body.uSeq = SocketNtohl(Packet.body.uSeq);
                Packet.body.uAck = SocketNtohl(Packet.body.uAck);
                // extract and clear meta bits
                Packet.head.uMeta = CommUDPUtilGetMetaTypeFromSeq(Packet.body.uSeq);
                Packet.body.uSeq &= ~(0x0f << COMMUDP_SEQ_META_SHIFT);

                #if COMM_PRINT > 1
                NetPrintf(("commudp: [%p] seq:0x%08x ack:0x%08x recv %d bytes from %a:%d (head.iLen=%d, meta=%d) at tick=%d\n",
                    pRef, Packet.body.uSeq, Packet.body.uAck, iLen, SockaddrInGetAddr(&Sin), SockaddrInGetPort(&Sin), Packet.head.iLen, Packet.head.uMeta, Packet.head.uWhen));
                #endif
                #if COMM_PRINT > 2
                NetPrintMem(&Packet.body, iLen, "cudp-recv");
                #endif
                
                if (Packet.body.uSeq == COMMUDP_RAW_PACKET_CONN || (Packet.body.uSeq == COMMUDP_RAW_PACKET_INIT) || (Packet.body.uSeq == COMMUDP_RAW_PACKET_POKE))
                {
                    NetPrintf(("commudp: [%p] got %s connident=0x%08x len=%d\n", pRef, g_strConnNames[Packet.body.uSeq], Packet.body.uAck, Packet.head.iLen));
                }
                // count the packet
                ++iCount;

                break;
            }
        }
    }

    // if we have meta chunk use that for matching with our connection ref
    if ((Packet.head.iLen >= COMMUDP_RAW_METATYPE1_SIZE) && (Packet.head.uMeta == 1))
    {
        int32_t iMetaOffset = ((Packet.body.uSeq == COMMUDP_RAW_PACKET_INIT) || (Packet.body.uSeq == COMMUDP_RAW_PACKET_POKE)) ? 4 : 0;
        
        // read meta type one chunk data
        uRClientIdent = ((uint32_t)Packet.body.aData[0+iMetaOffset])<<24 | ((uint32_t)Packet.body.aData[1+iMetaOffset])<<16 | ((uint32_t)Packet.body.aData[2+iMetaOffset])<<8 | (uint32_t)Packet.body.aData[3+iMetaOffset];
        uLClientIdent = ((uint32_t)Packet.body.aData[4+iMetaOffset])<<24 | ((uint32_t)Packet.body.aData[5+iMetaOffset])<<16 | ((uint32_t)Packet.body.aData[6+iMetaOffset])<<8 | (uint32_t)Packet.body.aData[7+iMetaOffset];
        
        // remove metadata from packet
        Packet.head.iLen -= COMMUDP_RAW_METATYPE1_SIZE;
        if ((Packet.head.iLen-iMetaOffset) > 0)
        {
            memmove(&Packet.body.aData[0+iMetaOffset], &Packet.body.aData[COMMUDP_RAW_METATYPE1_SIZE+iMetaOffset], Packet.head.iLen-iMetaOffset);
        }
        #if COMM_PRINT > 2
        NetPrintf(("commudp: [%p] processed metadata chunk\n", pRef));
        #endif
    }

    // walk port list and handle processing
    for (pRef = g_link; pRef != NULL; pRef = pRef->pLink)
    {
        // get latest tick
        uCurrTick = NetTick();

        // identify port to handle connection request
        if ((pListen == NULL) && (pSocket == pRef->pSocket) && (pRef->eState == LIST) && (Packet.head.iLen >= 0) &&
            ((Packet.body.uSeq == COMMUDP_RAW_PACKET_INIT) || (Packet.body.uSeq == COMMUDP_RAW_PACKET_CONN)) && (pRef->uConnIdent == Packet.body.uAck))
        {
            pListen = pRef;
        }

        if (Packet.head.iLen >= 0)
        {
            if (Packet.head.uMeta == 1)
            {
                bConnIdentMatch = (uRClientIdent == pRef->uRemClientIdent) && (uLClientIdent == pRef->uClientIdent);
                #if COMM_PRINT > 2
                NetPrintf(("commudp: [%p] matching with metadata: src=0x%08x(0x%08x) dst=0x%08x(0x%08x) match=%d\n", pRef,
                    uRClientIdent, pRef->uRemClientIdent, uLClientIdent, pRef->uClientIdent, bConnIdentMatch));
                #endif
            }
            else
            {
                // if this is an INIT or POKE, make sure the connident matches
                bConnIdentMatch = ((Packet.body.uSeq == COMMUDP_RAW_PACKET_INIT) || (Packet.body.uSeq == COMMUDP_RAW_PACKET_POKE)) ? (Packet.body.uAck == pRef->uConnIdent) : 1;
            }
        }

        // see if packet belongs to someone
        if ((Packet.head.iLen >= 0) && (pRef->eState != LIST) && (pRef->eState != CLOSE) && (pSocket == pRef->pSocket) &&
            (SockaddrCompare(&pRef->PeerAddr, &Sin) == 0) && (bConnIdentMatch))
        {
            // we got a packet.
            pRef->Common.bpackrcvd = TRUE;

            // transition to open state if we're getting data from peer
            if ((pRef->eState == CONN) && ((Packet.body.uSeq == COMMUDP_RAW_PACKET_UNREL) || (Packet.body.uSeq == COMMUDP_RAW_PACKET_DATA)))
            {
                NetPrintf(("commudp: [%p] transitioning to OPEN state due to received data from peer\n", pRef));
                pRef->eState = OPEN;
            }

            // do stats
            if (pRef->eState == OPEN)
            {
                pRef->Common.datarcvd += Packet.head.iLen;
                pRef->Common.packrcvd += 1;
            }

            // process an incoming packet
            if ((Packet.body.uSeq == COMMUDP_RAW_PACKET_INIT) ||
                (Packet.body.uSeq == COMMUDP_RAW_PACKET_POKE) ||
                (Packet.body.uSeq == COMMUDP_RAW_PACKET_CONN) ||
                (Packet.body.uSeq == COMMUDP_RAW_PACKET_DISC))
            {
                // handle connection setup/teardown
                _CommUDPProcessSetup(pRef, &Packet, &Sin, uCurrTick);
            }
            else if (pRef->eState != OPEN)
            {
                // ignore the packet
                continue;
            }
            else if (Packet.body.uSeq == COMMUDP_RAW_PACKET_NAK)
            {
                // remember we got something
                pRef->uRecvTick = Packet.head.uWhen;
                // resend the missing data
                _CommUDPProcessFlow(pRef, (RawUDPPacketHeadT *)&Packet, uCurrTick);
            }
            else if (Packet.body.uSeq > COMMUDP_SEQ_MULTI_INC)
            {
                RawUDPPacketHeadT Multi;
                uint32_t uOldSeq = pRef->uRcvSeq;
                int32_t iExtraSubPktCount = CommUDPUtilGetExtraSubPktCountFromSeq(Packet.body.uSeq);
                Multi.head.uWhen = Packet.head.uWhen;
                Multi.body.uSeq = _CommUDPSeqnDelta((Packet.body.uSeq & COMMUDP_SEQ_MASK), -iExtraSubPktCount);
                Multi.body.uAck = Packet.body.uAck;
                // remember we got something
                pRef->uRecvTick = Packet.head.uWhen;
                // process all the packets
                for (; iExtraSubPktCount >= 0; --iExtraSubPktCount)
                {
                    if (iExtraSubPktCount > 0)
                    {
                        Packet.head.iLen -= CommUDPUtilDecodeSubpacketSize(Packet.body.aData+Packet.head.iLen-1, &Multi.head.iLen);
                    }
                    else
                    {
                        Multi.head.iLen = Packet.head.iLen;
                    }
                    Packet.head.iLen -= Multi.head.iLen;

                    if (Packet.head.iLen < 0)
                    {
                        // we just received corrupt data, bail out
                        break;
                    }

                    // process the ack info
                    _CommUDPProcessFlow(pRef, &Multi, uCurrTick);
                    // process the input data and check for missing data (nak)
                    // (if nak, stop processing so we only send one nak packet)
                    if (_CommUDPProcessInput(pRef, &Multi, Packet.body.aData+ Packet.head.iLen, uCurrTick) < 0)
                    {
                        iExtraSubPktCount = 0;
                    }
                    // see if packets were saved
                    if ((iExtraSubPktCount > 0) && (uOldSeq != pRef->uRcvSeq))
                    {
                        pRef->Common.packsaved += 1;
                        #if COMM_PRINT
                        NetPrintf(("commudp: [%p] redundant packet %d prevented loss of packet %d\n", pRef, Packet.body.uSeq & COMMUDP_SEQ_MASK, uOldSeq));
                        #endif
                        uOldSeq = pRef->uRcvSeq;
                    }
                    // advance the packet sequence number

                    // if we sent a nak, multi.body.seq got wiped and count2 set to 0, so don't increment it.
                    if (Multi.body.uSeq >= COMMUDP_RAW_PACKET_UNREL)
                    {
                        Multi.body.uSeq = _CommUDPSeqnDelta(Multi.body.uSeq, 1);
                    }
                }
            }
            else
            {
                // remember we got something
                pRef->uRecvTick = Packet.head.uWhen;
                // process the ack info
                _CommUDPProcessFlow(pRef, (RawUDPPacketHeadT *)&Packet, uCurrTick);
                // save the data
                _CommUDPProcessInput(pRef, (RawUDPPacketHeadT *)&Packet, Packet.body.aData, uCurrTick);
            }
            // mark packet as processed
            Packet.head.iLen = -1;
        }

        // see if we are trying to connect
        if ((pRef->eState == CONN) && (uCurrTick-pRef->uSendTick > 1000))
        {
            _CommUDPProcessInit(pRef, uCurrTick);
        }

        // see if any output for this port
        if ((pRef->eState == OPEN) && (pRef->iSndNxt != pRef->iSndInp))
        {
            _CommUDPProcessOutput(pRef, uCurrTick);
        }

        // check for connection timeout
        if ((pRef->eState == OPEN) && (NetTickDiff(uCurrTick, pRef->uRecvTick) > 120*1000) && (NetTickDiff(uCurrTick, pRef->uSendTick) < 2000))
        {
            NetPrintf(("commudp: [%p] closing connection due to timeout\n", pRef));
            NetPrintf(("commudp: [%p] tick=%d, rtick=%d, stick=%d\n", pRef, uCurrTick, pRef->uRecvTick, pRef->uSendTick));
            _CommUDPClose(pRef, uCurrTick);
        }

        // see if we should run the penetrator
        if ((pRef->eState == LIST) && (pRef->PeerAddr.sa_family == AF_INET) &&
            (uCurrTick > pRef->uSendTick+PENETRATE_RATE))
        {
            // penetrate the firewall
            _CommUDPProcessPoke(pRef, uCurrTick);
        }

        // see if callback needs an idle tick
        if ((pRef->uGotEvent == 0) && (uCurrTick > pRef->uIdleTick+250))
        {
            pRef->uIdleTick = uCurrTick;
            pRef->uGotEvent |= 4;
        }

        // do callback if needed
        if ((pRef->iCallback == 0) && (pRef->uGotEvent != 0))
        {
            // limit callback access
            pRef->iCallback += 1;
            // callback the handler
            if (pRef->pCallProc != NULL)
            {
                pRef->pCallProc((CommRef *)pRef, pRef->uGotEvent);
            }
            else if (pRef->eState == OPEN)
            {
                #if COMM_PRINT
                NetPrintf(("commudp: [%p] no upper layer callback\n", pRef));
                #endif
            }
            // limit callback access
            pRef->iCallback -= 1;
            // reset event count
            pRef->uGotEvent = 0;
        }

        // see if we need a keepalive
        // do this after callback since callback often generates a
        // packet send that eliminates the need for the idle packet
        if ((pRef->eState == OPEN) && (pRef->iSndNxt == pRef->iSndInp))
        {
            int32_t timeout = NetTickDiff(uCurrTick, pRef->uSendTick);
            int32_t reliabletimeout = NetTickDiff(uCurrTick, pRef->uSendReliableTick);
            if (((reliabletimeout > BUSY_KEEPALIVE) && (pRef->uRcvAck != pRef->uRcvSeq)) ||
                ((reliabletimeout > BUSY_KEEPALIVE) && (pRef->iSndInp != pRef->iSndOut)) ||
                 (timeout > IDLE_KEEPALIVE) ||
                 (pRef->iRcvUnack >= UNACK_LIMIT))
            {
                // force reset of unack count just in case
                pRef->iRcvUnack = 0;
                // send a keepalive
                _CommUDPProcessAlive(pRef, uCurrTick);
            }
        }
    }

    // check for unclaimed connection packet (port mangling)
    if ((Packet.head.iLen >= 0) && (Sin.sa_family == AF_INET) &&
        ((Packet.body.uSeq == COMMUDP_RAW_PACKET_POKE) ||
         (Packet.body.uSeq == COMMUDP_RAW_PACKET_INIT) ||
         (Packet.body.uSeq == COMMUDP_RAW_PACKET_CONN)))
    {
        uint32_t bIgnored = FALSE;
        NetPrintf(("commudp: [%p] received %s packet from %a:%d\n", pRef, g_strConnNames[Packet.body.uSeq], SockaddrInGetAddr(&Sin), SockaddrInGetPort(&Sin)));
        
        // look for matching reference
        for (pRef = g_link; pRef != NULL; pRef = pRef->pLink)
        {
            if (((pRef->eState == CONN) || (pRef->eState == LIST)) && (pRef->PeerAddr.sa_family == AF_INET))
            {
                if (pRef->uConnIdent == Packet.body.uAck)
                {
                    // see if they are trying to connect to poke source but port is mangled
                    if ((pSocket == pRef->pSocket) && (SockaddrInGetAddr(&pRef->PeerAddr) == SockaddrInGetAddr(&Sin)) && (SockaddrInGetPort(&pRef->PeerAddr) != SockaddrInGetPort(&Sin)))
                    {
                        // assume poke source is masq, change port number to correspond
                        NetPrintf(("commudp: [%p] changing peer to %a:%d (was expecting %a:%d)\n", pRef, SockaddrInGetAddr(&Sin), SockaddrInGetPort(&Sin),
                            SockaddrInGetAddr(&pRef->PeerAddr), SockaddrInGetPort(&pRef->PeerAddr)));
                        ds_memcpy_s(&pRef->PeerAddr, sizeof(pRef->PeerAddr), &Sin, sizeof(Sin));
                    }
                    break;
                }
                else // remember we ignored a packet with non-matching connident
                {
                    bIgnored = TRUE;
                }
            }
        }
        if ((pRef == NULL) && (bIgnored == TRUE))
        {
            NetPrintf(("commudp: [%p] ignoring %s packet with connident 0x%08x\n", pRef, g_strConnNames[Packet.body.uSeq], Packet.body.uAck));
        }
    }

    // see if this was an initial connection request
    if ((pListen != NULL) && (Packet.head.iLen >= 0) && ((Packet.body.uSeq == COMMUDP_RAW_PACKET_INIT) || (Packet.body.uSeq == COMMUDP_RAW_PACKET_CONN)))
    {
        int32_t iPeerAddr = SockaddrInGetAddr(&pListen->PeerAddr);
        pRef = pListen;
        
        if ((pRef->uConnIdent == Packet.body.uAck) && ((iPeerAddr == 0) || (iPeerAddr == SockaddrInGetAddr(&Sin))))
        {
            NetPrintf(("commudp: [%p] transitioning to CONN state after receiving %s packet\n", pRef, g_strConnNames[Packet.body.uSeq]));
            // change to connecting state
            pRef->eState = CONN;
            // if we were listening without a peer address, set it now
            if (iPeerAddr == 0)
            {
                ds_memcpy_s(&pRef->PeerAddr, sizeof(pRef->PeerAddr), &Sin, sizeof(Sin));
            }
            // process init packet
            _CommUDPProcessSetup(pRef, &Packet, &Sin, uCurrTick);
        }
    }

    return(iCount);
}

/*F*************************************************************************************************/
/*!
    \Function    _CommUDPEvent

    \Description
        Main event function

    \Input *pSock   - socket
    \Input iFlags    - flags
    \Input *_ref    - to be completed

    \Output
        int32_t     - 0

    \Version 12/04/00 (GWS)

*/
/*************************************************************************************************F*/
static int32_t _CommUDPEvent(SocketT *pSock, int32_t iFlags, void *_ref)
{
    // remember we're in an event call
    g_inevent = 1;

    // see if we have exclusive access
    if (NetCritTry(&g_crit))
    {
        uint32_t uCurrTick = NetTick();
        // clear event counter before calling _CommUDPProcessThreadData to prevent possible unwanted recursion
        g_missed = 0;
        // process data as long as we get something
        while (_CommUDPThreadData(uCurrTick) > 0)
            ;
        // free access
        NetCritLeave(&g_crit);
    }
    else
    {
        g_missed += 1;
        #if COMM_PRINT
        NetPrintf(("commudp: missed %d events\n", g_missed));
        #endif
    }

    // leaving event call
    g_inevent = 0;

    // done for now
    return(0);
}


/*F*************************************************************************************************/
/*!
    \Function    _CommUDPEnlistRef

    \Description
        Add the given ref to the global linked list of references.

    \Input *pRef    - ref to add

    \Notes
        This also handles initialization of the global critical section global
        missed event counter.

    \Version 02/18/03 (JLB)
*/
/*************************************************************************************************F*/
static void _CommUDPEnlistRef(CommUDPRef *pRef)
{
    // if first ref, init global critical section
    if (g_link == NULL)
    {
        NetCritInit(&g_crit, "commudp-global");
        g_missed = 0;
        g_inevent = 0;
    }

    // add to linked list of ports
    NetCritEnter(&g_crit);
    pRef->pLink = g_link;
    g_link = pRef;
    NetCritLeave(&g_crit);
}

/*F*************************************************************************************************/
/*!
    \Function    _CommUDPDelistRef

    \Description
        Remove the given ref from the global linked list of references.    

    \Input *pRef    - ref to remove

    \Notes
        This also handles destruction of the global critical section.

    \Version 02/18/03 (JLB)
*/
/*************************************************************************************************F*/
static void _CommUDPDelistRef(CommUDPRef *pRef)
{
    CommUDPRef **ppLink;

    // remove from linked list of ports
    NetCritEnter(&g_crit);
    for (ppLink = &(g_link); *ppLink != pRef; ppLink = &((*ppLink)->pLink))
        ;
    *ppLink = pRef->pLink;
    NetCritLeave(&g_crit);
}

/*F*************************************************************************************************/
/*!
    \Function    _CommUDPListen0

    \Description
        Listen for a connection (private version)

    \Input *pRef        - reference pointer
    \Input *pSock       - fallback socket
    \Input *pBindAddr   - local port

    \Output
        int32_t         - negative=error, zero=ok

    \Version 12/04/00 (GWS)
*/
/*************************************************************************************************F*/
static int32_t _CommUDPListen0(CommUDPRef *pRef, SocketT *pSock, struct sockaddr *pBindAddr)
{
    int32_t iErr;
    CommUDPRef *pFind;
    struct sockaddr GlueAddr;

    // make sure in valid state
    if (pRef->eState != IDLE)
    {
        SocketClose(pSock);
        return(COMM_BADSTATE);
    }

    // reset to unresolved
    _CommUDPSetSocket(pRef, NULL);
    _CommUDPResetTransfer(pRef);

    // setup bind points
    ds_memclr(&GlueAddr, sizeof(GlueAddr));
    ds_memclr(&pRef->PeerAddr, sizeof(pRef->PeerAddr));

#if defined(DIRTYCODE_PC)
    /*
    the following line will fill in bindaddr with the IP addr previously set with
    SocketControl(... 'ladr' ...). If that selector was never used, then the IP
    address field of bindaddr will just be filled with 0.
    note: only required for multi-homed system (PC)
    */
    SocketInfo(NULL, 'ladr', 0, pBindAddr, sizeof(*pBindAddr));
#endif

    // see if there is an existing socket bound to this port
    for (pFind = g_link; pFind != NULL; pFind = pFind->pLink)
    {
        // dont check ourselves or unbound sockets
        if ((pFind == pRef) || (pFind->pSocket == NULL))
            continue;

        // see where this endpoint is bound
        if (SocketInfo(pFind->pSocket, 'bind', 0, &GlueAddr, sizeof(GlueAddr)) < 0)
            continue;

        /*
        see if the socket can be reused
            if the endpoint is virtual: we only compare the ports
            if the endpoint is not virtual:
                if bindaddr (specifying what we want to bind to) has ipaddr=0, we only compare the ports
                otherwise we compare the full sockaddr structs (i.e. family, port, addr)
        */
        if (SockaddrInGetPort(pBindAddr) == SockaddrInGetPort(&GlueAddr))
        {
            if ((SocketInfo(pFind->pSocket, 'virt', 0, NULL, 0) == TRUE) ||
                (SockaddrInGetAddr(pBindAddr) == 0) ||
                (SockaddrCompare(pBindAddr, &GlueAddr) == 0))
            {
                // share the socket
                _CommUDPSetSocket(pRef, pFind->pSocket);

                // dont need supplied socket
                SocketClose(pSock);
                break;
            }
        }
    }

    // create socket if no existing one
    if (pFind == NULL)
    {
        // bind the address to the socket
        iErr = SocketBind(pSock, pBindAddr, sizeof(*pBindAddr));
        if (iErr < 0)
        {
            NetPrintf(("commudp: [%p] bind to %d failed with %d\n", pRef, SockaddrInGetPort(pBindAddr), iErr));
            SockaddrInSetPort(pBindAddr, 0);
            if ((iErr = SocketBind(pSock, pBindAddr, sizeof(*pBindAddr))) < 0)
            {
                NetPrintf(("commudp: [%p] bind to 0 failed with result %d\n", pRef, iErr));
                pRef->eState = DEAD;
                SocketClose(pSock);
                return((iErr == SOCKERR_INVALID) ? COMM_PORTBOUND : COMM_UNEXPECTED);
            }
        }
        // use the supplied socket
        _CommUDPSetSocket(pRef, pSock);

        // setup for socket events
        SocketCallback(pSock, CALLB_RECV, 100, NULL, &_CommUDPEvent);
    }

    // put into listen mode
    pRef->eState = LIST;
    return(0);
}

/*F*************************************************************************************************/
/*!
    \Function    _CommUDPConnect0

    \Description
        Initiate a connection to a peer (private version)

    \Input *pRef        - reference pointer
    \Input *pSock       - socket
    \Input *pPeerAddr   - peer address

    \Output
        int32_t         - negative=error, zero=ok

    \Notes
        Does not currently perform dns translation

    \Version 12/04/00 (GWS)

*/
/*************************************************************************************************F*/
static int32_t _CommUDPConnect0(CommUDPRef *pRef, SocketT *pSock, struct sockaddr *pPeerAddr)
{
    // make sure in valid state
    if (pRef->eState != IDLE) {
        SocketClose(pSock);
        return(COMM_BADSTATE);
    }

    // reset to unresolved
    _CommUDPSetSocket(pRef, NULL);
    _CommUDPResetTransfer(pRef);

    // setup target info
    ds_memcpy_s(&pRef->PeerAddr, sizeof(pRef->PeerAddr), pPeerAddr, sizeof(*pPeerAddr));

    // save the socket
    _CommUDPSetSocket(pRef, pSock);

    // setup for callbacks
    SocketCallback(pSock, CALLB_RECV, 100, NULL, &_CommUDPEvent);

    // change the state
    pRef->eState = CONN;
    return(0);
}

/*** Public Functions ******************************************************************/

/*F*************************************************************************************************/
/*!
    \Function    CommUDPConstruct

    \Description
        Construct the class

    \Input iMaxWid  - max record width
    \Input iMaxInp  - input packet buffer size
    \Input iMaxOut  - output packet buffer size

    \Output
        CommUDPRef  - construct pointer

    \Notes
        Initialized winsock for first class. also creates linked
        list of all current instances of the class and worker thread
        to do most udp stuff.

    \Version 12/04/00 (GWS)

*/
/*************************************************************************************************F*/
CommUDPRef *CommUDPConstruct(int32_t iMaxWid, int32_t iMaxInp, int32_t iMaxOut)
{
    CommUDPRef *pRef;
    int32_t iMemGroup;
    void *pMemGroupUserData;
    
    // Query current mem group data
    DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData);

    // allocate class storage
    pRef = DirtyMemAlloc(sizeof(*pRef), COMMUDP_MEMID, iMemGroup, pMemGroupUserData);
    if (pRef == NULL)
        return(NULL);
    ds_memclr(pRef, sizeof(*pRef));
    pRef->Common.memgroup = iMemGroup;
    pRef->Common.memgrpusrdata = pMemGroupUserData;

    // initialize the callback routines
    pRef->Common.Construct = (CommAllConstructT *)CommUDPConstruct;
    pRef->Common.Destroy = (CommAllDestroyT *)CommUDPDestroy;
    pRef->Common.Resolve = (CommAllResolveT *)CommUDPResolve;
    pRef->Common.Unresolve = (CommAllUnresolveT *)CommUDPUnresolve;
    pRef->Common.Listen = (CommAllListenT *)CommUDPListen;
    pRef->Common.Unlisten = (CommAllUnlistenT *)CommUDPUnlisten;
    pRef->Common.Connect = (CommAllConnectT *)CommUDPConnect;
    pRef->Common.Unconnect = (CommAllUnconnectT *)CommUDPUnconnect;
    pRef->Common.Callback = (CommAllCallbackT *)CommUDPCallback;
    pRef->Common.Control = (CommAllControlT *)CommUDPControl;
    pRef->Common.Status = (CommAllStatusT *)CommUDPStatus;
    pRef->Common.Tick = (CommAllTickT *)CommUDPTick;
    pRef->Common.Send = (CommAllSendT *)CommUDPSend;
    pRef->Common.Peek = (CommAllPeekT *)CommUDPPeek;
    pRef->Common.Recv = (CommAllRecvT *)CommUDPRecv;

    // remember max packet width
    pRef->Common.maxwid = iMaxWid;
    pRef->Common.maxinp = iMaxInp;
    pRef->Common.maxout = iMaxOut;

    // allocate the buffers
    pRef->iRcvWid = sizeof(RawUDPPacketT)-sizeof(((RawUDPPacketT *)0)->body.aData)+iMaxWid+COMMUDP_MAX_METALEN;
    pRef->iRcvWid = (pRef->iRcvWid +3) & 0x7ffc;
    pRef->iRcvLen = pRef->iRcvWid * iMaxInp;
    pRef->pRcvBuf = (char *)DirtyMemAlloc(pRef->iRcvLen, COMMUDP_MEMID, pRef->Common.memgroup, pRef->Common.memgrpusrdata);
    pRef->iSndWid = sizeof(RawUDPPacketT)-sizeof(((RawUDPPacketT *)0)->body.aData)+iMaxWid+COMMUDP_MAX_METALEN;
    pRef->iSndWid = (pRef->iSndWid+3) & 0x7ffc;
    pRef->iSndLen = pRef->iSndWid * iMaxOut;
    pRef->pSndBuf = (char *)DirtyMemAlloc(pRef->iSndLen, COMMUDP_MEMID, pRef->Common.memgroup, pRef->Common.memgrpusrdata);

    // reset the socket
    _CommUDPSetSocket(pRef, NULL);
    
    // reset peer address
    ds_memclr(&pRef->PeerAddr, sizeof(pRef->PeerAddr));

    // reset the state
    pRef->eState = IDLE;
    pRef->uConnIdent = 0;
    pRef->uUnackLimit = UNACK_LIMIT;
    pRef->uRedundantLimit = REDUNDANT_LIMIT_DEFAULT;

    // add to port list
    _CommUDPEnlistRef(pRef);

    return(pRef);
}

/*F*************************************************************************************************/
/*!
    \Function    CommUDPDestroy

    \Description
        Destruct the class

    \Input *pRef    - reference pointer

    \Version 12/04/00 (GWS)

*/
/*************************************************************************************************F*/
void CommUDPDestroy(CommUDPRef *pRef)
{
    CommUDPRef *pFind;
    uint32_t uCurrTick = NetTick();

    // flush final data
    _CommUDPFlush(pRef, 200, uCurrTick);

    // remove from port list
    _CommUDPDelistRef(pRef);

    // if port is open, close it
    if (pRef->eState == OPEN)
        _CommUDPClose(pRef, uCurrTick);

    // kill the socket
    if (pRef->pSocket != NULL) {
        // see if socket shared
        for (pFind = g_link; pFind != NULL; pFind = pFind->pLink) {
            if (pFind->pSocket == pRef->pSocket)
                break;
        }
        // if we are only user of this socket
        if (pFind == NULL) {
            SocketClose(pRef->pSocket);
            _CommUDPSetSocket(pRef, NULL);
        }
    }

    // if last ref, destroy global critical section
    if (g_link == NULL)
    {
        NetCritKill(&g_crit);
    }

    // release resources
    DirtyMemFree(pRef->pRcvBuf, COMMUDP_MEMID, pRef->Common.memgroup, pRef->Common.memgrpusrdata);
    DirtyMemFree(pRef->pSndBuf, COMMUDP_MEMID, pRef->Common.memgroup, pRef->Common.memgrpusrdata);
    DirtyMemFree(pRef, COMMUDP_MEMID, pRef->Common.memgroup, pRef->Common.memgrpusrdata);
}

/*F*************************************************************************************************/
/*!
    \Function    CommUDPCallback

    \Description
        Set upper layer callback

    \Input *pRef        - reference pointer
    \Input *pCallback    - socket generating callback

    \Version 12/04/00 (GWS)

*/
/*************************************************************************************************F*/
void CommUDPCallback(CommUDPRef *pRef, void (*pCallback)(void *pRef, int32_t iEvent))
{
    NetCritEnter(&g_crit);
    pRef->pCallProc = pCallback;
    pRef->uGotEvent |= 2;
    NetCritLeave(&g_crit);
}

/*F*************************************************************************************************/
/*!
    \Function    CommUDPResolve

    \Description
        Resolve an address

    \Input *pRef    - endpoint
    \Input *pAddr   - resolve address
    \Input *pBuf    - target buffer
    \Input iLen     - target length (min 64 bytes)
    \Input cDiv     - divider char

    \Output
        int32_t         - <0=error, 0=complete (COMM_NOERROR), >0=in progress (COMM_PENDING)

    \Notes
        Target list is always double null terminated allowing null
        to be used as the divider character if desired. when COMM_PENDING
        is returned, target buffer is set to "~" until completion.

    \Version 12/04/00 (GWS)

*/
/*************************************************************************************************F*/
int32_t CommUDPResolve(CommUDPRef *pRef, const char *pAddr, char *pBuf, int32_t iLen, char cDiv)
{
    NetPrintf(("commudp: [%p] resolve functionality not supported\n", pRef));
    return(-1);
}

/*F*************************************************************************************************/
/*!
    \Function    CommUDPUnresolve

    \Description
        Stop the resolver

    \Input *pRef    - reference pointer

    \Version 12/04/00 (GWS)

*/
/*************************************************************************************************F*/
void CommUDPUnresolve(CommUDPRef *pRef)
{
}

/*F*************************************************************************************************/
/*!
    \Function    CommUDPUnlisten

    \Description
        Stop listening

    \Input *pRef    - reference pointer

    \Output
        int32_t     - negative=error, zero=ok

    \Version 12/04/00 (GWS)
*/
/*************************************************************************************************F*/
int32_t CommUDPUnlisten(CommUDPRef *pRef)
{
    uint32_t uCurrTick = NetTick();

    // flush final data
    _CommUDPFlush(pRef, 200, uCurrTick);

    // never close listening socket (it is shared)
    if (pRef->eState == LIST)
    {
        _CommUDPSetSocket(pRef, NULL);
    }

    // get rid of socket if presernt
    if (pRef->pSocket != NULL)
    {
        // attempt to close socket
        _CommUDPClose(pRef, uCurrTick);
        // done with socket
        SocketClose(pRef->pSocket);
        _CommUDPSetSocket(pRef, NULL);
    }

    // return to idle mode
    pRef->eState = IDLE;
    return(0);
}

/*F*************************************************************************************************/
/*!
    \Function    CommUDPUnconnect

    \Description
        Terminate a connection

    \Input *pRef    - reference pointer

    \Output
        int32_t     - negative=error, zero=ok


    \Version 12/04/00 (GWS)

*/
/*************************************************************************************************F*/
int32_t CommUDPUnconnect(CommUDPRef *pRef)
{
    uint32_t uCurrTick = NetTick();

    // flush final data
    _CommUDPFlush(pRef, 200, uCurrTick);

    // never close listening socket (it is shared)
    if (pRef->eState == LIST)
    {
        _CommUDPSetSocket(pRef, NULL);
    }

    // get rid of socket if presernt
    if (pRef->pSocket != NULL)
    {
        // attempt to close socket
        _CommUDPClose(pRef, uCurrTick);
        // done with socket
        SocketClose(pRef->pSocket);
        _CommUDPSetSocket(pRef, NULL);
    }

    // return to idle mode
    pRef->eState = IDLE;
    return(0);
}

/*F*************************************************************************************************/
/*!
    \Function    CommUDPStatus

    \Description
        Return current stream status

    \Input *pRef    - reference pointer

    \Output
        int32_t     - CONNECTING, OFFLINE, ONLINE or FAILURE

    \Version 12/04/00 (GWS)

*/
/*************************************************************************************************F*/
int32_t CommUDPStatus(CommUDPRef *pRef)
{
    // return state
    if ((pRef->eState == CONN) || (pRef->eState == LIST))
        return(COMM_CONNECTING);
    if ((pRef->eState == IDLE) || (pRef->eState == CLOSE))
        return(COMM_OFFLINE);
    if (pRef->eState == OPEN)
        return(COMM_ONLINE);
    return(COMM_FAILURE);
}

/*F*************************************************************************************************/
/*!
    \Function    CommUDPControl

    \Description
        Set connection behavior.

    \Input *pRef     - reference pointer
    \Input iControl - control selector
    \Input iValue   - selector specfic
    \Input *pValue  - selector specific

    \Output
        int32_t     - negative=error, else selector result

    \Notes
        iControl can be one of the following:

        \verbatim
            'clid' - client identifier
            'meta' - set metatype (default=0)
            'rcid' - remote client identifier
            'rlmt' - redundant packet size limit (default = REDUNDANT_LIMIT_DEFAULT)
            'ulmt' - unacknowledged packet limit (2k default)
        \endverbatim

    \Version 02/20/2007 (jbrookes)
*/
/*************************************************************************************************F*/
int32_t CommUDPControl(CommUDPRef *pRef, int32_t iControl, int32_t iValue, void *pValue)
{
    if (iControl == 'clid')
    {
        pRef->uClientIdent = iValue;
        return(0);
    }
    if (iControl == 'meta')
    {
        NetPrintf(("commudp: [%p] setting metatype=%d\n", pRef, iValue));
        pRef->uMetaType = iValue;
        return(0);
    }
    if (iControl == 'rcid')
    {
        pRef->uRemClientIdent = iValue;
        return(0);
    }
    if (iControl == 'rlmt')
    {
        const int32_t iMaxLimit = sizeof(((RawUDPPacketT *)0)->body.aData);
        if (iValue == 0)
        {
            iValue = REDUNDANT_LIMIT_DEFAULT;
        }
        if (iValue >= iMaxLimit)
        {
            iValue = iMaxLimit;
        }
        NetPrintf(("commudp: [%p] redundant limit changed from %d bytes to %d bytes\n", pRef, pRef->uRedundantLimit, iValue));
        pRef->uRedundantLimit = iValue;
        return(0);
    }
    if (iControl == 'ulmt')
    {
        pRef->uUnackLimit = iValue;
        NetPrintf(("commudp: [%p] setting ulimit to %d bytes\n", pRef, pRef->uUnackLimit));
        return(0);
    }
    // unhandled; pass through to socket module if we have a socket
    if (pRef->pSocket != NULL)
    {
        return(SocketControl(pRef->pSocket, iControl, iValue, pValue, NULL));
    }
    return(-1);
}

/*F*************************************************************************************************/
/*!
    \Function    CommUDPTick

    \Description
        Return current tick

    \Input *pRef    - reference pointer

    \Output
        uint32_t    - elaped milliseconds

    \Version 12/04/00 (GWS)
*/
/*************************************************************************************************F*/
uint32_t CommUDPTick(CommUDPRef *pRef)
{
    return(NetTick());
}

/*F*************************************************************************************************/
/*!
    \Function    CommUDPSend

    \Description
        Send a packet

    \Input *pRef    - reference pointer
    \Input *pBuffer - pointer to data
    \Input iLength  - length of data
    \Input uFlags   - COMM_FLAGS_*

    \Output
        int32_t     - negative=error, zero=buffer full (temp fail), positive=queue position (ok)

    \Notes
        Zero length packets may not be sent (they are used for buffer query)

    \Version 12/04/00 (GWS)
*/
/*************************************************************************************************F*/
// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
int32_t CommUDPSend(CommUDPRef *pRef, const void *pBuffer, int32_t iLength, uint32_t uFlags, uint64_t *pLatency)
// FIFA_END
{
    RawUDPPacketT PacketBuffer, *pPacket;
    uint32_t uCurrTick = NetTick();
    int32_t iPos, iMetaLen, iResult=1; // result=1 is default (min queue) result

    // make sure port is open
    if (pRef->eState != OPEN)
    {
        return(COMM_BADSTATE);
    }

    // if metachunk enabled, adjust size 
    iMetaLen = CommUDPUtilGetMetaSize(pRef->uMetaType);

    // return error for oversized packets
    if ((iLength+iMetaLen) > (signed)(pRef->iSndWid-(sizeof(RawUDPPacketT)-sizeof(((RawUDPPacketT *)0)->body.aData))))
    {
        NetPrintf(("commudp: [%p] oversized packet send (%d bytes)\n", pRef, iLength));
        return(COMM_MINBUFFER);
    }

    /* check for zero-length packets, which cannot be sent (they are used for acks); instead, treat them as successful,
       which means the queue position is returned */
    if (iLength == 0)
    {
        iPos = (((pRef->iSndInp+pRef->iSndLen)-pRef->iSndOut)%pRef->iSndLen)/pRef->iSndWid;
        return(iPos+1);
    }

    // get packet buffer
    if (uFlags & COMM_FLAGS_UNRELIABLE)
    {
        // unreliable packets are staged locally and flushed immediately
        pPacket = &PacketBuffer;
    }
    else
    {
        // make sure output buffer isn't full
        if ((pRef->iSndInp + pRef->iSndWid) % pRef->iSndLen == pRef->iSndOut)
        {
            NetPrintfVerbose((COMM_PRINT, 0, "commudp: [%p] send overflow (connident=0x%08x)\n", pRef, pRef->uConnIdent));
            return(0);
        }
        // reference packet buffer in output queue
        pPacket = (RawUDPPacketT *) &(pRef->pSndBuf[pRef->iSndInp]);
    }

    // copy the packet to the buffer
    ds_memcpy(pPacket->body.aData, pBuffer, iLength);
    pPacket->head.iLen = iLength;
    // set the send time
    pPacket->head.uWhen = uCurrTick;

    // handle unreliable send
    if (uFlags & COMM_FLAGS_UNRELIABLE)
    {
        int32_t iErr = -1;
        NetCritEnter(&g_crit);
        
        // set up and send an unreliable packet
        pPacket->body.uSeq = pRef->uUnreliableSndSeq;
        pPacket->body.uAck = _CommUDPSeqnDelta(pRef->uRcvSeq, -1);
        if (uFlags & COMM_FLAGS_BROADCAST)
        {
            struct sockaddr PeerAddr;
            ds_memcpy_s(&PeerAddr, sizeof(PeerAddr), &pRef->PeerAddr, sizeof(pRef->PeerAddr));
            SockaddrInSetAddr(&PeerAddr, 0xffffffff);
            iErr = _CommUDPWrite(pRef, pPacket, &PeerAddr, uCurrTick);
        }
        else
        {
            iErr = _CommUDPWrite(pRef, pPacket, &pRef->PeerAddr, uCurrTick);
        }

        // calculate new sequence number
        if (++pRef->uUnreliableSndSeq >= COMMUDP_RAW_PACKET_DATA)
        {
            pRef->uUnreliableSndSeq = COMMUDP_RAW_PACKET_UNREL;
        }
        
        NetCritLeave(&g_crit);
        // if send failed, return buffer-full(0) or error
        if (iErr < 0)
        {
            iResult = (pRef->uSndErr == SOCKERR_NONE) ? 0 : iErr;
        }
    }
    else
    {
// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
#ifdef ENABLED_LATENCY_METER
		if (pLatency != NULL)
		{
			pLatency[OUT_COMMUDP_QUEUE] = NetProcTime();
		}
		pPacket->head.pLatency = pLatency;
#endif
// FIFA_END

        // set the data fields
        pPacket->body.uSeq = pRef->uSndSeq;
        pRef->uSndSeq = _CommUDPSeqnDelta(pRef->uSndSeq, 1);

        pPacket->body.uAck = _CommUDPSeqnDelta(pRef->uRcvSeq, -1);

        // add the packet to the queue
        pRef->iSndInp = (pRef->iSndInp+pRef->iSndWid) % pRef->iSndLen;
        iPos = (((pRef->iSndInp+pRef->iSndLen)-pRef->iSndOut)%pRef->iSndLen)/pRef->iSndWid;

        // try to send packet immediately if buffer is at least half empty
        if (iPos < (pRef->Common.maxout/2))
        {
            NetCritEnter(&g_crit);
            _CommUDPProcessOutput(pRef, uCurrTick);

            // process incoming if we missed event
            if (g_missed != 0)
            {
                // clear event counter before calling _CommUDPProcessThreadData to prevent possible unwanted recursion
                g_missed = 0;
                // process data as long as we get something
                while (_CommUDPThreadData(uCurrTick) > 0)
                    ;
                NetPrintfVerbose((COMM_PRINT, 0, "commudp: [%p] processing %d after send\n", pRef, g_missed));
            }
            NetCritLeave(&g_crit);
        }
        // return buffer depth
        if (iPos > 0)
        {
            iResult = iPos;
        }
    }

    return(iResult);
}

/*F*************************************************************************************************/
/*!
    \Function    CommUDPPeek

    \Description
        Peek at waiting packet

    \Input *pRef    - reference pointer
    \Input *pTarget - target buffer
    \Input iLength  - buffer length
    \Input *pWhen   - tick received at

    \Output
        int32_t         - negative=nothing pending, else packet length

    \Version 12/04/00 (GWS)

*/
/*************************************************************************************************F*/
// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
int32_t CommUDPPeek(CommUDPRef *pRef, void *pTarget, int32_t iLength, uint32_t *pWhen, uint64_t *pLatency)
// FIFA_END
{
    RawUDPPacketT *pPacket;

    // see if a packet is available
    if (pRef->iRcvOut == pRef->iRcvInp)
        return(COMM_NODATA);

    // point to the packet
    pPacket = (RawUDPPacketT *) &(pRef->pRcvBuf[pRef->iRcvOut]);

    // copy data?
    if (iLength > 0)
    {
        // make sure enough space is available
        if (iLength < pPacket->head.iLen)
            return(COMM_MINBUFFER);
       
        // copy over the data portion
        ds_memcpy(pTarget, pPacket->body.aData, pPacket->head.iLen);
    }
        
    // get the timestamp
    if (pWhen != NULL)
        *pWhen = pPacket->head.uWhen;
    
// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
#ifdef ENABLED_LATENCY_METER
	if (pLatency != NULL)
	{
		ds_memcpy(pLatency, pPacket->head.aLatency, sizeof(pPacket->head.aLatency));
	}
#endif
// FIFA_END

    // return packet data length
    return(pPacket->head.iLen);
}

/*F*************************************************************************************************/
/*!
    \Function    CommUDPRecv

    \Description
        Receive a packet from the buffer

    \Input *pRef    - reference pointer
    \Input *pTarget - target buffer
    \Input iLength  - buffer length
    \Input *pWhen   - tick received at

    \Output
        int32_t     - negative=error, else packet length

    \Version 12/04/00 (GWS)

*/
/*************************************************************************************************F*/
// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
int32_t CommUDPRecv(CommUDPRef *pRef, void *pTarget, int32_t iLength, uint32_t *pWhen, uint64_t *pLatency)
// FIFA_END
{
    // use peek to remove the data
	// FIFA_BEGIN | DIVERGENCE | mmisalucha | 2019-12-24 | Input latency tracking
    int32_t iLen = CommUDPPeek(pRef, pTarget, iLength, pWhen, pLatency);
	//FIFA_END
    if (iLen >= 0)
        pRef->iRcvOut = (pRef->iRcvOut+pRef->iRcvWid)%pRef->iRcvLen;
    // all done
    return(iLen);
}

/*F*************************************************************************************************/
/*!
    \Function    CommUDPListen

    \Description
        Listen for a connection

    \Input *pRef    - reference pointer
    \Input *pAddr   - port to listen on (only :port portion used)

    \Output
        int32_t     - negative=error, zero=ok

    \Version 12/04/00 (GWS)

*/
/*************************************************************************************************F*/
int32_t CommUDPListen(CommUDPRef *pRef, const char *pAddr)
{
    int32_t iErr, iListenPort, iConnPort;
    uint32_t uPoke;
    SocketT *pSock;
    struct sockaddr BindAddr;

    // setup bind points
    SockaddrInit(&BindAddr, AF_INET);

    // parse at least port
    if ((SockaddrInParse2(&uPoke, &iListenPort, &iConnPort, pAddr) & 0x2) != 0x2)
    {
        return(COMM_BADADDRESS);
    }
    SockaddrInSetPort(&BindAddr, iListenPort);

    // create socket in case its needed
    pSock = SocketOpen(AF_INET, SOCK_DGRAM, 0);
    if (pSock == NULL)
    {
        return(COMM_NORESOURCE);
    }

    // let common code finish up
    iErr = _CommUDPListen0(pRef, pSock, &BindAddr);

    // set connection identifier
    _CommUDPSetConnID(pRef, pAddr);

    NetPrintf(("commudp: [%p] listen err=%d, bind=%d, connident=0x%08x\n", pRef, iErr, iListenPort, pRef->uConnIdent));

    // see if we should setup peer address
    if ((iErr == 0) && (uPoke != 0))
    {
        if (iConnPort == 0)
        {
            iConnPort = iListenPort+1;
        }

        NetPrintf(("commudp: [%p] poke address=%08x:%d\n", pRef, uPoke, iConnPort));
        SockaddrInit(&pRef->PeerAddr, AF_INET);
        SockaddrInSetAddr(&pRef->PeerAddr, uPoke);
        SockaddrInSetPort(&pRef->PeerAddr, iConnPort);
    }

    // clear any previous receive errors
    pRef->uSndErr = 0;
    return(iErr);
}

/*F*************************************************************************************************/
/*!
    \Function    CommUDPConnect

    \Description
        Initiate a connection to a peer

    \Input *pRef    - reference pointer
    \Input *pAddr   - address in ip-address:port form

    \Output
        int32_t     - negative=error, zero=ok

    \Notes
        Does not currently perform dns translation

    \Version 12/04/00 (GWS)

*/
/*************************************************************************************************F*/
int32_t CommUDPConnect(CommUDPRef *pRef, const char *pAddr)
{
    int32_t iErr, iConnPort, iListenPort;
    uint32_t uAddr;
    CommUDPRef *pFind;
    SocketT *sock;
    struct sockaddr BindAddr, GlueAddr, PeerAddr;

    // setup target info
    SockaddrInit(&PeerAddr, AF_INET);
    SockaddrInit(&BindAddr, AF_INET);

    // parse addr - make sure we have at least an address and port
    iErr = SockaddrInParse2(&uAddr, &iListenPort, &iConnPort, pAddr);
    if ((iErr & 3) != 3)
    {
        return(COMM_BADADDRESS);
    }

    // if we don't have an alternate connect port, connect=listen
    if (iConnPort == 0)
    {
        iConnPort = iListenPort++;
    }

    // set listen port
    SockaddrInSetPort(&BindAddr, iListenPort);

    // set connection identifier
    _CommUDPSetConnID(pRef, pAddr);
    NetPrintf(("commudp: [%p] connect addr=%08x, bind=%d, peer=%d connident=0x%08x\n",
        pRef, uAddr, iListenPort, iConnPort, pRef->uConnIdent));

#if defined(DIRTYCODE_PC)
    /*
    the following line will fill in bindaddr with the IP addr previously set with
    SocketControl(... 'ladr' ...). If that selector was never used, then the IP
    address field of bindaddr will just be filled with 0.
    note: only required for multi-homed system (PC)
    */
    SocketInfo(NULL, 'ladr', 0, &BindAddr, sizeof(BindAddr));
#endif

    // see if there is an existing socket bound to this port
    for (pFind = g_link, sock = NULL; pFind != NULL; pFind = pFind->pLink)
    {
        // dont check ourselves or unbound sockets
        if ((pFind == pRef) || (pFind->pSocket == NULL))
            continue;

        // see where this endpoint is bound
        if (SocketInfo(pFind->pSocket, 'bind', 0, &GlueAddr, sizeof(GlueAddr)) < 0)
            continue;

        /*
        see if the socket can be reused
            if the endpoint is virtual: we only compare the ports
            if the endpoint is not virtual:
                if bindaddr (specifying what we want to bind to) has ipaddr=0, we only compare the ports
                otherwise we compare the full sockaddr structs (i.e. family, port, addr)
        */
        if (SockaddrInGetPort(&BindAddr) == SockaddrInGetPort(&GlueAddr))
        {
            if ((SocketInfo(pFind->pSocket, 'virt', 0, NULL, 0) == TRUE) ||
                (SockaddrInGetAddr(&BindAddr) == 0) ||
                (SockaddrCompare(&BindAddr, &GlueAddr) == 0))
            {
                // share the socket
                sock = pFind->pSocket;
                break;
            }
        }
    }

    // create the actual socket
    if (sock == NULL)
    {
        sock = SocketOpen(AF_INET, SOCK_DGRAM, 0);
        if (sock == NULL)
        {
            return(COMM_NORESOURCE);
        }
        
        // bind socket
        if ((iErr = SocketBind(sock, &BindAddr, sizeof(BindAddr))) < 0)
        {
            NetPrintf(("commudp: [%p] bind to %d failed with %d\n", pRef, iListenPort, iErr));
            SockaddrInSetPort(&BindAddr, 0);
            if ((iErr = SocketBind(sock, &BindAddr, sizeof(BindAddr))) < 0)
            {
                NetPrintf(("commudp: [%p] bind to 0 failed with result %d\n", pRef, iErr));
                SocketClose(sock);
                return(COMM_UNEXPECTED);
            }
            else
            {
                SocketInfo(sock, 'bind', 0, &BindAddr, sizeof(BindAddr));
                NetPrintf(("commudp: [%p] bound socket to port %d\n", pRef, SockaddrInGetPort(&BindAddr)));
            }
        }
    }

    // set connect sockaddr
    SockaddrInSetAddr(&PeerAddr, uAddr);
    SockaddrInSetPort(&PeerAddr, iConnPort);

    // clear any previous receive errors
    pRef->uSndErr = 0;

    // pass to common handler
    return(_CommUDPConnect0(pRef, sock, &PeerAddr));
}

