/*H*************************************************************************************************/
/*!
    \File    dirtynet.c

    \Description
        Platform-independent network related routines.

    \Copyright
        Copyright (c) Electronic Arts 2002-2018

    \Version 1.0 01/02/2002 (gschaefer) First Version
    \Version 1.1 01/27/2003 (jbrookes)  Split from dirtynetwin.c
*/
/*************************************************************************************************H*/


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

#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#include "DirtySDK/dirtysock.h"
#include "DirtySDK/dirtysock/dirtymem.h"
#include "DirtySDK/dirtysock/dirtynet.h"

#include "dirtynetpriv.h"

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

//! 30s hostname cache timeout
#define DIRTYNET_HOSTNAMECACHELIFETIME  (30*1000)

//! verbose logging of dirtynet packet queue operations
#define DIRTYNET_PACKETQUEUEDEBUG       (DIRTYCODE_LOGGING && FALSE)

//! verbose logging of dirtynet rate estimation
#define DIRTYNET_RATEDEBUG              (DIRTYCODE_LOGGING && FALSE)

//! maximum allowable packet queue size
#define DIRTYNET_PACKETQUEUEMAX         (1024)

//! minimum throttle rate supported
#define DIRTYNET_MIN_THROTTLE_RATE      (1460)

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

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

//! socket hostname cache entry
typedef struct SocketHostnameCacheEntryT
{
    char strDnsName[256];
    uint32_t uAddress;
    uint32_t uTimer;
} SocketHostnameCacheEntryT;

//! socket hostname cache
struct SocketHostnameCacheT
{
    int32_t iMaxEntries;
    int32_t iMemGroup;
    void *pMemGroupUserData;
    NetCritT Crit;
    SocketHostnameCacheEntryT CacheEntries[1];  //!< variable-length cache entry list
};

//! socket packet queue
struct SocketPacketQueueT
{
    int32_t iMemGroup;
    void *pMemGroupUserData;

    int16_t iNumPackets;        //!< number of packets in the queue
    int16_t iMaxPackets;        //!< max queue size
    int16_t iPacketHead;        //!< current packet queue head
    int16_t iPacketTail;        //!< current packet queue tail

    uint32_t uLatency;          //!< simulated packet latency target, in milliseconds
    uint32_t uDeviation;        //!< simulated packet deviation target, in milliseconds
    uint32_t uPacketLoss;       //!< packet loss percentage, 16.16 fractional integer

    uint32_t uPacketDrop;       //!< number of packets overwritten due to queue overflow
    uint32_t uPacketMax;        //!< maximum number of packets in the queue (high water mark)

    uint32_t uLatencyTime;      //!< current amount of latency in the packet queue
    int32_t iDeviationTime;     //!< current deviation

    SocketPacketQueueEntryT aPacketQueue[1]; //!< variable-length queue entry list
};

#ifndef DIRTYCODE_NX
//! socket address map entry
struct SocketAddrMapEntryT
{
    int32_t iRefCount;
    int32_t iVirtualAddress;
    struct sockaddr_in6 SockAddr6;
};
#endif

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

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

// Private variables


// Public variables


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

#ifndef DIRTYCODE_NX
/*F********************************************************************************/
/*!
    \Function _SockaddrIn6Identify

    \Description
        Identify IPv6 sockaddr based on specified matching sequence (must come at
        the start of address bytes).

    \Input *pAddr6              - address to identify
    \Input *pCheckVal           - bytes to check
    \Input iValLen              - length of byte sequence to check

    \Output
        uint8_t                 - TRUE if the address matches, else FALSE

    \Version 04/12/2016 (jbrookes)
*/
/********************************************************************************F*/
static uint8_t _SockaddrIn6Identify(const struct sockaddr_in6 *pAddr6, const uint8_t *pCheckVal, int32_t iValLen)
{
    uint8_t bResult = !memcmp(pCheckVal, pAddr6->sin6_addr.s6_addr, iValLen) ? TRUE : FALSE;
    return(bResult);
}

/*F********************************************************************************/
/*!
    \Function _SockaddrIn6IsIPv4

    \Description
        Identify if specified sockaddr_in6 is an IPv4-mapped IPv6 address

    \Input *pAddr6              - address to identify

    \Output
        uint8_t                 - TRUE if the address is IPv4-mapped, else FALSE

    \Version 04/12/2016 (jbrookes)
*/
/********************************************************************************F*/
static uint8_t _SockaddrIn6IsIPv4(const struct sockaddr_in6 *pAddr6)
{
    const uint8_t aIpv4Prefix[]  = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff };
    return(_SockaddrIn6Identify(pAddr6, aIpv4Prefix, sizeof(aIpv4Prefix)));
}

/*F********************************************************************************/
/*!
    \Function _SockaddrIn6IsNAT64

    \Description
        Identify if specified sockaddr_in6 is a NAT64 IPv6 address

    \Input *pAddr6              - address to identify

    \Output
        uint8_t                 - TRUE if the address is NAT64, else FALSE

    \Version 04/12/2016 (jbrookes)
*/
/********************************************************************************F*/
static uint8_t _SockaddrIn6IsNAT64(const struct sockaddr_in6 *pAddr6)
{
    const uint8_t aNat64Prefix[] = { 0x00, 0x64, 0xff, 0x9b };
    return(_SockaddrIn6Identify(pAddr6, aNat64Prefix, sizeof(aNat64Prefix)));
}

/*F********************************************************************************/
/*!
    \Function _SockaddrIn6IsZero

    \Description
        Identify if specified sockaddr_in6 is zero (unspecified)

    \Input *pAddr6              - address to identify

    \Output
        uint8_t                 - TRUE if the address is zero, else FALSE

    \Version 04/22/2016 (jbrookes)
*/
/********************************************************************************F*/
static uint8_t _SockaddrIn6IsZero(const struct sockaddr_in6 *pAddr6)
{
    const uint8_t aIpv6Zero[]  = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
    return(_SockaddrIn6Identify(pAddr6, aIpv6Zero, sizeof(aIpv6Zero)));
}

/*F*************************************************************************************************/
/*!
\Function _SockaddrIn6SetAddrText

\Description
Set Internet address component of sockaddr_in6 struct from textural address.

\Input *pAddr6      -  pointer to ipv6 address
\Input *pStr        - pointer to source ipv6 address

\Output
int32_t         - zero=no error, negative=error

\Notes
See _SockaddrIn6GetAddrText for format considerations and references

\Version 08/28/2017 (jbrookes)
*/
/*************************************************************************************************F*/
static int32_t _SockaddrIn6SetAddrText(struct sockaddr_in6 *pAddr6, const char *pStr)
{
    uint16_t aAddrWords[8], *pWords, uWordVal;
    int32_t iWordIdx, iWordCt, iSkipIdx, iWriteIdx;
    const char *pEnd, *pDot;
    uint32_t uAddr32 = 0;

    // see if we have dot notation (e.g. nat64, ipv4-mapped)
    if ((pDot = strchr(pStr, '.')) != NULL)
    {
        // find start of dot notation number
        for (pDot -= 1; isalnum(*pDot) && (pDot > pStr); pDot -= 1)
            ;
        // get 32 bit address encoded in dot notation
        uAddr32 = SocketInTextGetAddr(pDot + 1);
    }

    // init word array
    ds_memclr(aAddrWords, sizeof(aAddrWords));

    // convert address words, remember if we had a skip
    for (iWordIdx = 0, iSkipIdx = -1; (*pStr != '\0') && (iWordIdx < 8); pStr += 1)
    {
        uWordVal = SocketHtons(strtol(pStr, (char **)&pEnd, 16));
        pStr = pEnd;

        if ((*pStr == ':') || (*pStr == '\0'))
        {
            aAddrWords[iWordIdx++] = uWordVal;
        }
        if ((*pStr == ':') && (pStr[1] == ':'))
        {
            iSkipIdx = iWordIdx;
            pStr += 1;
        }
        if ((pStr == pDot) && (uAddr32 != 0))
        {
            aAddrWords[iWordIdx++] = SocketHtons((uint16_t)(uAddr32 >> 16));
            aAddrWords[iWordIdx++] = SocketHtons((uint16_t)(uAddr32 >> 0));
            break;
        }

        if (*pStr == '\0')
        {
            break;
        }
    }

    // copy to sockaddr, with skip
    for (iWordCt = iWordIdx, iWordIdx = iWriteIdx = 0, pWords = (uint16_t *)pAddr6->sin6_addr.s6_addr; iWriteIdx < 8; )
    {
        if (iWriteIdx == iSkipIdx)
        {
            for (iWordCt = 8 - iWordCt; iWordCt > 0; iWordCt -= 1)
            {
                pWords[iWriteIdx++] = 0;
            }
        }
        else
        {
            pWords[iWriteIdx++] = aAddrWords[iWordIdx++];
        }
    }

    return(0);
}
/*F*************************************************************************************************/
/*!
    \Function _SockaddrIn6GetAddrText

    \Description
        Return Internet address component of sockaddr_in6 struct in textual form (see below).

    \Input *pAddr6  - pointer to ipv6 address
    \Input *pStr    - pointer to storage for text address
    \Input iLen     - length of buffer

    \Output
        char *      - returns pStr on success, NULL on failure

    \Notes
        IPv6 textual format guidelines are outlined in https://tools.ietf.org/html/rfc4291#section-2.2
        and later refined in https://tools.ietf.org/html/rfc5952, which narrows the range of
        supported options to standardize are more rigid specification.  This implementation
        follows the more limited set of specifications outlined in rfc5952.  The short version is:

        - Preferred form is x:x:x:x:x:x:x:x, where the 'x's are one of four hexadecimal digits of
        the eight 16-bit pieces of the address
        - Leading zeros within a field MUST be suppressed
        - The use of :: indicates one or more groups of 16 bits of zeros; it can appear only once
        in an address
        - The :: symbole MUST be used to its maximum capability.  It MUST NOT be used to shorten
        just one 16-bit field
        - If there are two or more sets of 16-bit zeros of equal length, the left-most MUST be
        shortened
        - Alphabetic characters in the address MUST be represented in lowercase
        - It is RECOMMENDED to use mixed notation if the address can be distinguished as having
        an IPv4 address embedded in the lower 32 bits solely from the address field through
        the use of a well-known prefix.

    \Version 08/28/2017 (jbrookes)
*/
/*************************************************************************************************F*/
static char *_SockaddrIn6GetAddrText(const struct sockaddr_in6 *pAddr6, char *pStr, int32_t iLen)
{
    uint16_t aAddrWords[8], *pWords;
    int32_t iWord, iOffset;
    int32_t iZeroStart, iZeroCount;
    int32_t iZeroStartTmp, iZeroCountTmp;
    char strAddr32[16] = "";

    // convert to words in host format
    for (iWord = 0, pWords = (uint16_t *)pAddr6->sin6_addr.s6_addr; iWord < 8; iWord += 1)
    {
        aAddrWords[iWord] = SocketNtohs(pWords[iWord]);
    }

    // if this is a mixed-notation address convert the final 32bits to a dot-notation address string
    if (_SockaddrIn6IsIPv4(pAddr6) || _SockaddrIn6IsNAT64(pAddr6))
    {
        uint32_t uAddr32 = (uint32_t)aAddrWords[6] << 16 | (uint32_t)aAddrWords[7];
        SocketInAddrGetText(uAddr32, strAddr32, sizeof(strAddr32));
    }

    // find longest stretch of two or more zeros (if there is one)
    for (iWord = iZeroStart = iZeroCount = iZeroCountTmp = iZeroStartTmp = 0; iWord < 8; iWord += 1)
    {
        if (aAddrWords[iWord] == 0)
        {
            if (iZeroCountTmp == 0)
            {
                iZeroStartTmp = iWord;
            }
            iZeroCountTmp += 1;
        }
        else
        {
            iZeroCountTmp = 0;
        }

        if (iZeroCountTmp > iZeroCount)
        {
            iZeroStart = iZeroStartTmp;
            iZeroCount = iZeroCountTmp;
        }
    }

    // format address string
    for (iWord = 0, iOffset = 0; iWord < 8; iWord += 1)
    {
        if ((iWord != iZeroStart) || (iZeroCount < 2))
        {
            iOffset += ds_snzprintf(pStr + iOffset, iLen - iOffset, "%x", aAddrWords[iWord]);
            if (iWord < 7)
            {
                iOffset += ds_snzprintf(pStr + iOffset, iLen - iOffset, ":");
            }
        }
        else
        {
            iOffset += ds_snzprintf(pStr + iOffset, iLen - iOffset, (iWord == 0) ? "::" : ":");
            iWord += iZeroCount - 1;
        }

        // output dot portion of mixed-notation address, if present
        if ((strAddr32[0] != '\0') && (iWord == 5))
        {
            iOffset += ds_snzprintf(pStr + iOffset, iLen - iOffset, "%s", strAddr32);
            iWord += 2;
        }
    }

    // return to caller
    return(pStr);
}

#endif
/*F*************************************************************************************************/
/*!
    \Function _SockaddrIn4SetAddrText

    \Description
        Set Internet address component of sockaddr struct from textual address (a.b.c.d).

    \Input *pAddr   - sockaddr structure
    \Input *pStr    - textual address

    \Output
        int32_t     - zero=no error, negative=error

    \Version 10/04/1999 (gschaefer)
*/
/*************************************************************************************************F*/
static int32_t _SockaddrIn4SetAddrText(struct sockaddr *pAddr, const char *pStr)
{
    uint8_t *pIpAddr = (uint8_t *)(pAddr->sa_data+2);
    int32_t iOctet;

    for (iOctet = 0; iOctet < 4; iOctet += 1, pStr += 1)
    {
        pIpAddr[iOctet] = '\0';
        while ((*pStr >= '0') && (*pStr <= '9'))
        {
            pIpAddr[iOctet] = (pIpAddr[iOctet]*10) + (*pStr++ & 15);
        }
        if ((iOctet < 3) && (*pStr != '.'))
        {
            pIpAddr[0] = pIpAddr[1] = pIpAddr[2] = pIpAddr[3] = '\0';
            return(-1);
        }
    }

    return(0);
}


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


/*F*************************************************************************************************/
/*!
    \Function SockaddrCompare

    \Description
        Compare two sockaddr structs and see if address is the same. This is different
        from simple binary compare because only relevent fields are checked.

    \Input *pAddr1  - address #1
    \Input *pAddr2  - address to compare with address #1

    \Output
        int32_t         - zero=no error, negative=error

    \Version 08/30/1999 (gschaefer)
*/
/*************************************************************************************************F*/
int32_t SockaddrCompare(const struct sockaddr *pAddr1, const struct sockaddr *pAddr2)
{
    int32_t len = sizeof(*pAddr1)-sizeof(pAddr1->sa_family);

    // make sure address family matches
    if (pAddr1->sa_family != pAddr2->sa_family)
    {
        return(pAddr1->sa_family- pAddr2->sa_family);
    }

    // do type specific comparison
    if (pAddr1->sa_family == AF_INET)
    {
        // compare port and address
        len = 2 + 4;
    }
    else if (pAddr1->sa_family == AF_INET6)
    {
        // compare port, flow info, and address
        len = 2 + 4 + 8;
    }

    // binary compare of address data
    return(memcmp(pAddr1->sa_data, pAddr2->sa_data, len));
}

/*F*************************************************************************************************/
/*!
    \Function SockaddrInSetAddrText

    \Description
        Set Internet address component of sockaddr struct from textual address.
        Important: Only works with AF_INET addresses for nx

    \Input *pAddr   - sockaddr structure
    \Input *pStr    - textual address

    \Output
        int32_t     - zero=no error, negative=error

    \Version 10/04/1999 (gschaefer)
*/
/*************************************************************************************************F*/
int32_t SockaddrInSetAddrText(struct sockaddr *pAddr, const char *pStr)
{
    int32_t iResult = -1;
    if (pAddr->sa_family == AF_INET)
    {
        iResult = _SockaddrIn4SetAddrText(pAddr, pStr);
    }
    #ifndef DIRTYCODE_NX
    else if (pAddr->sa_family == AF_INET6)
    {
        iResult = _SockaddrIn6SetAddrText((struct sockaddr_in6 *)pAddr, pStr);
    }
    #endif
    return(iResult);
}

/*F*************************************************************************************************/
/*!
    \Function SockaddrInGetAddrText

    \Description
        Convert a sockaddr into textual form based on address family

    \Input *pAddr   - sockaddr struct
    \Input *pStr    - address buffer
    \Input iLen     - address length

    \Output
        char *      - returns str on success, NULL on failure

    \Version 10/04/1999 (gschaefer)
*/
/*************************************************************************************************F*/
char *SockaddrInGetAddrText(const struct sockaddr *pAddr, char *pStr, int32_t iLen)
{
    char *pResult = NULL;
    if (pAddr->sa_family == AF_INET)
    {
        pResult = SocketInAddrGetText(SockaddrInGetAddr(pAddr), pStr, iLen);
    }
    #ifndef DIRTYCODE_NX
    else if (pAddr->sa_family == AF_INET6)
    {
        pResult = _SockaddrIn6GetAddrText((const struct sockaddr_in6 *)pAddr, pStr, iLen);
    }
    #endif
    return(pResult);
}

/*F*************************************************************************************************/
/*!
    \Function    SockaddrInParse

    \Description
        Convert textual internet address:port into sockaddr structure

    \Input *pAddr   - sockaddr to fill in
    \Input *pParse  - textual address

    \Output
        int32_t         - flags:
            0=parsed nothing
            1=parsed addr
            2=parsed port
            3=parsed addr+port

    \Version 11/23/2002 (gschaefer)
*/
/*************************************************************************************************F*/
int32_t SockaddrInParse(struct sockaddr *pAddr, const char *pParse)
{
    int32_t iReturn = 0, iPort = 0;
    uint32_t uAddr = 0;

    // init the address
    SockaddrInit(pAddr, AF_INET);

    // parse addr:port
    iReturn = SockaddrInParse2(&uAddr, &iPort, NULL, pParse);

    // set addr:port in sockaddr
    SockaddrInSetAddr(pAddr, uAddr);
    SockaddrInSetPort(pAddr, iPort);

    // return parse info
    return(iReturn);
}

/*F*************************************************************************************************/
/*!
    \Function    SockaddrInParse2

    \Description
        Convert textual internet address:port into sockaddr structure

        If the textual internet address:port is followed by a second :port, the second port
        is optionally parsed into pPort2, if not NULL.

    \Input *pAddr   - address to fill in
    \Input *pPort   - port to fill in
    \Input *pPort2  - second port to fill in
    \Input *pParse  - textual address

    \Output
        int32_t         - flags:
            0=parsed nothing
            1=parsed addr
            2=parsed port
            3=parsed addr+port
            4=parsed port2

    \Version 11/23/02 (GWS) First Version
*/
/*************************************************************************************************F*/
int32_t SockaddrInParse2(uint32_t *pAddr, int32_t *pPort, int32_t *pPort2, const char *pParse)
{
    int32_t iReturn = 0;
    uint32_t uVal;

    // skip embedded white-space
    while ((*pParse > 0) && (*pParse <= ' '))
    {
        ++pParse;
    }

    // parse the address (no dns for listen)
    for (uVal = 0; ((*pParse >= '0') && (*pParse <= '9')) || (*pParse == '.'); ++pParse)
    {
        // either add or shift
        if (*pParse != '.')
        {
            uVal = (uVal - (uVal & 255)) + ((uVal & 255) * 10) + (*pParse & 15);
        }
        else
        {
            uVal <<= 8;
        }
    }
    if ((*pAddr = uVal) != 0)
    {
        iReturn |= 1;
    }

    // skip non-port info
    while ((*pParse != ':') && (*pParse != 0))
    {
        ++pParse;
    }

    // parse the port
    uVal = 0;
    if (*pParse == ':')
    {
        for (++pParse; (*pParse >= '0') && (*pParse <= '9'); ++pParse)
        {
            uVal = (uVal * 10) + (*pParse & 15);
        }
        iReturn |= 2;
    }
    *pPort = (int32_t)uVal;

    // parse port2 (optional)
    if (pPort2 != NULL)
    {
        uVal = 0;
        if (*pParse == ':')
        {
            for (++pParse; (*pParse >= '0') && (*pParse <= '9'); ++pParse)
            {
                uVal = (uVal * 10) + (*pParse & 15);
            }
            iReturn |= 4;
        }
        *pPort2 = (int32_t)uVal;
    }

    // return the address
    return(iReturn);
}

/*F*************************************************************************************************/
/*!
    \Function SocketInAddrGetText

    \Description
        Convert 32-bit internet address into textual form.

    \Input uAddr    - address
    \Input *pStr    - [out] address buffer
    \Input iLen     - address length

    \Output
        char *      - returns str on success, NULL on failure

    \Version 06/17/2009 (jbrookes)
*/
/*************************************************************************************************F*/
char *SocketInAddrGetText(uint32_t uAddr, char *pStr, int32_t iLen)
{
    uint8_t uAddrByte[4];
    int32_t iIndex;
    char *pStrStart = pStr;

    uAddrByte[0] = (uint8_t)(uAddr>>24);
    uAddrByte[1] = (uint8_t)(uAddr>>16);
    uAddrByte[2] = (uint8_t)(uAddr>>8);
    uAddrByte[3] = (uint8_t)(uAddr>>0);

    for (iIndex = 0; iIndex < 4; iIndex += 1)
    {
        uint32_t uNumber = uAddrByte[iIndex];
        if (uNumber > 99)
        {
            *pStr++ = (char)('0' + (uNumber / 100));
            uNumber %= 100;
            *pStr++ = (char)('0' + (uNumber / 10));
            uNumber %= 10;
        }
        if (uNumber > 9)
        {
            *pStr++ = (char)('0' + (uNumber / 10));
            uNumber %= 10;
        }
        *pStr++ = (char)('0' + uNumber);
        if (iIndex < 3)
        {
            *pStr++ = '.';
        }
    }
    *pStr = '\0';
    return(pStrStart);
}

/*F*************************************************************************************************/
/*!
    \Function    SocketInTextGetAddr

    \Description
        Convert textual internet address into 32-bit integer form

    \Input *pAddrText   - textual address

    \Output
        int32_t         - integer form

    \Version 11/23/02 (JLB) First Version

*/
/*************************************************************************************************F*/
int32_t SocketInTextGetAddr(const char *pAddrText)
{
    struct sockaddr SockAddr;
    int32_t iAddr = 0;

    SockaddrInit(&SockAddr, AF_INET);
    if (SockaddrInSetAddrText(&SockAddr, pAddrText) == 0)
    {
        iAddr = SockaddrInGetAddr(&SockAddr);
    }
    return(iAddr);
}

/*F*************************************************************************************************/
/*!
    \Function    SocketHtons

    \Description
        Convert uint16_t from host to network byte order

    \Input uAddr    - value to convert

    \Output
        uint16_t    - converted value

    \Version 10/04/1999 (gschaefer)
*/
/*************************************************************************************************F*/
uint16_t SocketHtons(uint16_t uAddr)
{
    uint8_t uNetw[2];
    ds_memcpy_s(uNetw, sizeof(uNetw), &uAddr, sizeof(uAddr));
    return((uNetw[0]<<8)|(uNetw[1]<<0));
}

/*F*************************************************************************************************/
/*!
    \Function    SocketHtonl

    \Description
        Convert uint32_t from host to network byte order.

    \Input uAddr    - value to convert

    \Output
        uint32_t    - converted value

    \Version 10/04/1999 (gschaefer)
*/
/*************************************************************************************************F*/
uint32_t SocketHtonl(uint32_t uAddr)
{
    uint8_t uNetw[4];
    ds_memcpy_s(uNetw, sizeof(uNetw), &uAddr, sizeof(uAddr));
    return((((((uNetw[0]<<8)|uNetw[1])<<8)|uNetw[2])<<8)|uNetw[3]);
}

/*F*************************************************************************************************/
/*!
    \Function    SocketNtohs

    \Description
        Convert uint16_t from network to host byte order.

    \Input uAddr  - value to convert

    \Output
        uint16_t  - converted value

    \Version 10/0/99 (GWS) First Version

*/
/*************************************************************************************************F*/
uint16_t SocketNtohs(uint16_t uAddr)
{
    uint8_t uNetw[2];
    uNetw[1] = (uint8_t)uAddr;
    uAddr >>= 8;
    uNetw[0] = (uint8_t)uAddr;
    ds_memcpy_s(&uAddr, sizeof(uAddr), uNetw, sizeof(uNetw));
    return(uAddr);
}

/*F*************************************************************************************************/
/*!
    \Function    SocketNtohl

    \Description
        Convert uint32_t from network to host byte order.

    \Input uAddr     - value to convert

    \Output
        uint32_t    - converted value

    \Version 10/04/1999 (gschaefer)
*/
/*************************************************************************************************F*/
uint32_t SocketNtohl(uint32_t uAddr)
{
    uint8_t uNetw[4];
    uNetw[3] = (uint8_t)uAddr;
    uAddr >>= 8;
    uNetw[2] = (uint8_t)uAddr;
    uAddr >>= 8;
    uNetw[1] = (uint8_t)uAddr;
    uAddr >>= 8;
    uNetw[0] = (uint8_t)uAddr;
    ds_memcpy_s(&uAddr, sizeof(uAddr), uNetw, sizeof(uNetw));
    return(uAddr);
}

/*
    HostName Cache functions
*/

/*F********************************************************************************/
/*!
    \Function SocketHostnameCacheCreate

    \Description
        Create short-term hostname (DNS) cache

    \Input iMemGroup            - memgroup to alloc/free with
    \Input *pMemGroupUserData   - memgroup user data to alloc/free with

    \Output
        SocketHostnameCacheT *  - hostname cache or NULL on failure

    \Version 10/09/2013 (jbrookes)
*/
/********************************************************************************F*/
SocketHostnameCacheT *SocketHostnameCacheCreate(int32_t iMemGroup, void *pMemGroupUserData)
{
    const int32_t iMaxEntries = 16;
    int32_t iCacheSize = sizeof(SocketHostnameCacheT) + (iMaxEntries * sizeof(SocketHostnameCacheEntryT));
    SocketHostnameCacheT *pCache;

    // alloc and init cache
    if ((pCache = DirtyMemAlloc(iCacheSize, SOCKET_MEMID, iMemGroup, pMemGroupUserData)) == NULL)
    {
        NetPrintf(("dirtynet: could not alloc %d bytes for hostname cache\n", iCacheSize));
        return(NULL);
    }
    ds_memclr(pCache, iCacheSize);

    // set base info
    pCache->iMaxEntries = iMaxEntries;
    pCache->iMemGroup = iMemGroup;
    pCache->pMemGroupUserData = pMemGroupUserData;

    // initialize crit
    NetCritInit2(&pCache->Crit, "HostnameCache", NETCRIT_OPTION_SINGLETHREADENABLE);

    // return to caller
    return(pCache);
}

/*F********************************************************************************/
/*!
    \Function SocketHostnameCacheDestroy

    \Description
        Destroy short-term hostname (DNS) cache

    \Input *pCache      - hostname cache

    \Version 10/09/2013 (jbrookes)
*/
/********************************************************************************F*/
void SocketHostnameCacheDestroy(SocketHostnameCacheT *pCache)
{
    NetCritKill(&pCache->Crit);
    DirtyMemFree(pCache, SOCKET_MEMID, pCache->iMemGroup, pCache->pMemGroupUserData);
}

/*F********************************************************************************/
/*!
    \Function SocketHostnameCacheAdd

    \Description
        Add hostname and address to hostname cache

    \Input *pCache      - hostname cache
    \Input *pStrHost    - hostname to add
    \Input uAddress     - address of hostname
    \Input iVerbose     - debug level

    \Version 10/09/2013 (jbrookes)
*/
/********************************************************************************F*/
void SocketHostnameCacheAdd(SocketHostnameCacheT *pCache, const char *pStrHost, uint32_t uAddress, int32_t iVerbose)
{
    SocketHostnameCacheEntryT *pCacheEntry;
    int32_t iCacheIdx;

    // if we're already in the cache, bail
    if (SocketHostnameCacheGet(pCache, pStrHost, 0) != 0)
    {
        return;
    }

    // scan cache for an open entry
    NetCritEnter(&pCache->Crit);
    for (iCacheIdx = 0; iCacheIdx < pCache->iMaxEntries; iCacheIdx += 1)
    {
        pCacheEntry = &pCache->CacheEntries[iCacheIdx];
        if (pCacheEntry->uAddress == 0)
        {
            NetPrintfVerbose((iVerbose, 1, "dirtynet: adding hostname cache entry %s/%a\n", pStrHost, uAddress));
            ds_strnzcpy(pCacheEntry->strDnsName, pStrHost, sizeof(pCacheEntry->strDnsName));
            pCacheEntry->uAddress = uAddress;
            pCacheEntry->uTimer = NetTick();
            break;
        }
    }
    NetCritLeave(&pCache->Crit);
}

/*F********************************************************************************/
/*!
    \Function SocketHostnameCacheGet

    \Description
        Get address for hostname from cache, if available.

    \Input *pCache      - hostname cache
    \Input *pStrHost    - hostname to add
    \Input iVerbose     - debug level

    \Output
        uint32_t        - address for hostname, or zero if not in cache

    \Version 10/09/2013 (jbrookes)
*/
/********************************************************************************F*/
uint32_t SocketHostnameCacheGet(SocketHostnameCacheT *pCache, const char *pStrHost, int32_t iVerbose)
{
    SocketHostnameCacheEntryT *pCacheEntry;
    uint32_t uAddress;
    int32_t iCacheIdx;

    // scan cache for dns entry
    NetCritEnter(&pCache->Crit);
    for (iCacheIdx = 0, uAddress = 0; iCacheIdx < pCache->iMaxEntries; iCacheIdx += 1)
    {
        pCacheEntry = &pCache->CacheEntries[iCacheIdx];
        // skip empty entries
        if (pCacheEntry->strDnsName[0] == '\0')
        {
            continue;
        }
        // check for entry we want
        if (!strcmp(pCacheEntry->strDnsName, pStrHost))
        {
            NetPrintfVerbose((iVerbose, 0, "dirtynet: %s=%a [cache]\n", pCacheEntry->strDnsName, pCacheEntry->uAddress));
            uAddress = pCache->CacheEntries[iCacheIdx].uAddress;
            break;
        }
    }
    NetCritLeave(&pCache->Crit);
    return(uAddress);
}

/*F********************************************************************************/
/*!
    \Function SocketHostnameCacheDel

    \Description
        Remove hostname cache entry from the cache, if it exists.  pStrHost is
        checked if non-NULL and uAddress is checked if non-zero (note that both
        can be checked if desired).

    \Input *pCache      - hostname cache
    \Input *pStrHost    - hostname of cache entry to delete, or NULL
    \Input uAddress     - address of cache entry to delete, or zero
    \Input iVerbose     - debug level

    \Version 09/23/2016 (jbrookes)
*/
/********************************************************************************F*/
void SocketHostnameCacheDel(SocketHostnameCacheT *pCache, const char *pStrHost, uint32_t uAddress, int32_t iVerbose)
{
    SocketHostnameCacheEntryT *pCacheEntry;
    int32_t iCacheIdx;

    // scan cache for dns entry
    NetCritEnter(&pCache->Crit);
    for (iCacheIdx = 0; iCacheIdx < pCache->iMaxEntries; iCacheIdx += 1)
    {
        pCacheEntry = &pCache->CacheEntries[iCacheIdx];
        // skip empty entries
        if (pCacheEntry->strDnsName[0] == '\0')
        {
            continue;
        }
        // check for hostname match
        if ((pStrHost != NULL) && strcmp(pCacheEntry->strDnsName, pStrHost))
        {
            continue;
        }
        // check for address match
        if ((uAddress != 0) && (pCacheEntry->uAddress != uAddress))
        {
            continue;
        }
        // found a match; delete the entry
        NetPrintfVerbose((iVerbose, 1, "dirtynet: deleting hostname cache entry %s/%a\n", pCacheEntry->strDnsName, pCacheEntry->uAddress));
        ds_memclr(pCacheEntry, sizeof(*pCacheEntry));
        break;
    }
    NetCritLeave(&pCache->Crit);
}

/*F********************************************************************************/
/*!
    \Function SocketHostnameCacheProcess

    \Description
        Process the hostname cache, clear expired cache entries

    \Input *pCache      - hostname cache
    \Input iVerbose     - debug level

    \Version 04/23/2019 (eesponda)
*/
/********************************************************************************F*/
void SocketHostnameCacheProcess(SocketHostnameCacheT *pCache, int32_t iVerbose)
{
    SocketHostnameCacheEntryT *pCacheEntry;
    uint32_t uCurTick;
    int32_t iCacheIdx;

    if (!NetCritTry(&pCache->Crit))
    {
        return;
    }

    // scan cache for dns entry
    for (iCacheIdx = 0, uCurTick = NetTick(); iCacheIdx < pCache->iMaxEntries; iCacheIdx += 1)
    {
        pCacheEntry = &pCache->CacheEntries[iCacheIdx];
        // skip empty entries
        if (pCacheEntry->strDnsName[0] == '\0')
        {
            continue;
        }
        // check for expiration
        if (NetTickDiff(uCurTick, pCacheEntry->uTimer) > DIRTYNET_HOSTNAMECACHELIFETIME)
        {
            NetPrintfVerbose((iVerbose, 1, "dirtynet: expiring hostname cache entry %s/%a\n", pCacheEntry->strDnsName, pCacheEntry->uAddress));
            ds_memclr(pCacheEntry, sizeof(*pCacheEntry));
        }
    }
    NetCritLeave(&pCache->Crit);
}

/*F********************************************************************************/
/*!
    \Function SocketHostnameAddRef

    \Description
        Check for in-progress DNS requests we can piggyback on, instead of issuing
        a new request.

    \Input **ppHostList - list of active lookups
    \Input *pHost       - current lookup
    \Input bUseRef      - force using a new entry if bUseRef=FALSE (should normally be TRUE)

    \Output
        HostentT *      - Pre-existing DNS request we have refcounted, or NULL

    \Version 01/16/2014 (jbrookes)
*/
/********************************************************************************F*/
HostentT *SocketHostnameAddRef(HostentT **ppHostList, HostentT *pHost, uint8_t bUseRef)
{
    HostentT *pHost2 = NULL;

    // look for an in-progress refcounted lookup
    NetCritEnter(NULL);
    if (bUseRef == TRUE)
    {
        for (pHost2 = *ppHostList; pHost2 != NULL; pHost2 = pHost2->pNext)
        {
            if (!strcmp(pHost2->name, pHost->name) && !pHost2->done)
            {
                break;
            }
        }
    }

    // new lookup, so add to list
    if (pHost2 == NULL)
    {
        pHost->refcount = 1;
        pHost->pNext = *ppHostList;
        *ppHostList = pHost;
        pHost = NULL;
    }
    else // found an in-progress lookup, so piggyback on it
    {
        pHost = pHost2;
        pHost->refcount += 1;
        NetPrintfVerbose((SocketInfo(NULL, 'spam', 0, NULL, 0), 0, "dirtynet: %s lookup refcounted (%d refs)\n", pHost->name, pHost->refcount));
    }

    NetCritLeave(NULL);
    return(pHost);
}

/*F********************************************************************************/
/*!
    \Function SocketHostnameListProcess

    \Description
        Process list of in-progress DNS requests, disposing of those that are
        completed and no longer referenced.

    \Input **ppHostList - list of active lookups
    \Input iMemGroup    - memgroup hostname lookup records are allocated with
    \Input *pMemGroupUserData - memgroup userdata hostname lookup records are allocated with

    \Notes
        This function is called from the SocketIdle thread, which is already guarded
        by the global critical section.  It is therefore assumed that it does not
        need to be explicitly guarded here.

    \Version 01/16/2014 (jbrookes)
*/
/********************************************************************************F*/
void SocketHostnameListProcess(HostentT **ppHostList, int32_t iMemGroup, void *pMemGroupUserData)
{
    HostentT **ppHost;
    for (ppHost = ppHostList; *ppHost != NULL;)
    {
        if ((*ppHost)->refcount == 0)
        {
            HostentT *pHost = *ppHost;
            *ppHost = (*ppHost)->pNext;
            DirtyMemFree(pHost, SOCKET_MEMID, iMemGroup, pMemGroupUserData);
        }
        else
        {
            ppHost = &(*ppHost)->pNext;
        }
    }
}

/*
    Packet Queue functions
*/

/*F********************************************************************************/
/*!
    \Function SocketPacketQueueCreate

    \Description
        Create a packet queue

    \Input iMaxPackets          - size of queue, in packets (max 127)
    \Input iMemGroup            - memgroup to alloc/free with
    \Input *pMemGroupUserData   - memgroup user data to alloc/free with

    \Output
        SocketPacketQueueT *    - packet queue, or NULL on failure

    \Version 02/21/2014 (jbrookes)
*/
/********************************************************************************F*/
SocketPacketQueueT *SocketPacketQueueCreate(int32_t iMaxPackets, int32_t iMemGroup, void *pMemGroupUserData)
{
    SocketPacketQueueT *pPacketQueue;
    int32_t iQueueSize;

    // enforce min/max queue sizes
    iMaxPackets = DS_CLAMP(iMaxPackets, 1, DIRTYNET_PACKETQUEUEMAX);

    // calculate memory required for queue
    iQueueSize = sizeof(*pPacketQueue) + ((iMaxPackets-1) * sizeof(pPacketQueue->aPacketQueue[0]));

    // alloc and init queue
    if ((pPacketQueue = DirtyMemAlloc(iQueueSize, SOCKET_MEMID, iMemGroup, pMemGroupUserData)) == NULL)
    {
        NetPrintf(("dirtynet: could not alloc %d bytes for packet queue\n", iQueueSize));
        return(NULL);
    }
    ds_memclr(pPacketQueue, iQueueSize);

    // set base info
    pPacketQueue->iNumPackets = 0;
    pPacketQueue->iMaxPackets = iMaxPackets;
    pPacketQueue->iMemGroup = iMemGroup;
    pPacketQueue->pMemGroupUserData = pMemGroupUserData;

    // latency/packet loss simulation setup
    pPacketQueue->uLatencyTime = NetTick();

    //$$temp - testing
    //pPacketQueue->uLatency = 100;
    //pPacketQueue->uDeviation = 5;
    //pPacketQueue->uPacketLoss = 5*65536;

    // return queue to caller
    return(pPacketQueue);
}

/*F********************************************************************************/
/*!
    \Function SocketPacketQueueDestroy

    \Description
        Destroy packet queue

    \Input *pPacketQueue    - packet queue to destroy

    \Version 02/21/2014 (jbrookes)
*/
/********************************************************************************F*/
void SocketPacketQueueDestroy(SocketPacketQueueT *pPacketQueue)
{
    DirtyMemFree(pPacketQueue, SOCKET_MEMID, pPacketQueue->iMemGroup, pPacketQueue->pMemGroupUserData);
}

/*F********************************************************************************/
/*!
    \Function SocketPacketQueueResize

    \Description
        Resize a packet queue, if the max packet size is different

    \Input *pPacketQueue        - packet queue to resize (may be null)
    \Input iMaxPackets          - new size of queue, in packets (max 127)
    \Input iMemGroup            - memgroup to alloc/free with
    \Input *pMemGroupUserData   - memgroup user data to alloc/free with

    \Output
        SocketPacketQueueT * - pointer to resized packet queue

    \Notes
        If the new max queue size is less than the number of packets in the current
        queue, packets will be overwritten in the usual manner (older discarded in
        favor of newer).

    \Version 05/30/2014 (jbrookes)
*/
/********************************************************************************F*/
SocketPacketQueueT *SocketPacketQueueResize(SocketPacketQueueT *pPacketQueue, int32_t iMaxPackets, int32_t iMemGroup, void *pMemGroupUserData)
{
    uint8_t aPacketData[SOCKET_MAXUDPRECV];
    SocketPacketQueueT *pNewPacketQueue;
    struct sockaddr PacketAddr;
    int32_t iPacketSize;

    // enforce min/max queue sizes
    iMaxPackets = DS_CLAMP(iMaxPackets, 1, DIRTYNET_PACKETQUEUEMAX);

    // if we have a queue and it's already the right size, return it
    if ((pPacketQueue != NULL) && (pPacketQueue->iMaxPackets == iMaxPackets))
    {
        return(pPacketQueue);
    }

    // create new queue
    NetPrintf(("dirtynet: [%p] re-creating socket packet queue with %d max packets\n", pPacketQueue, iMaxPackets));
    if ((pNewPacketQueue = SocketPacketQueueCreate(iMaxPackets, iMemGroup, pMemGroupUserData)) == NULL)
    {
        NetPrintf(("dirtynet: could not allocate new packet queue\n"));
        return(pPacketQueue);
    }

    // copy old data (if any) over, and destroy the old packet queue
    if (pPacketQueue != NULL)
    {
        while ((iPacketSize = SocketPacketQueueRem(pPacketQueue, aPacketData, sizeof(aPacketData), &PacketAddr)) > 0)
        {
            SocketPacketQueueAdd(pNewPacketQueue, aPacketData, iPacketSize, &PacketAddr);
        }
        SocketPacketQueueDestroy(pPacketQueue);
    }

    // return resized queue to caller
    return(pNewPacketQueue);
}

/*F********************************************************************************/
/*!
    \Function SocketPacketQueueAdd

    \Description
        Add a packet to packet queue

    \Input *pPacketQueue    - packet queue to add to
    \Input *pPacketData     - packet data to add to queue
    \Input iPacketSize      - size of packet data
    \Input *pPacketAddr     - remote address associated with packet

    \Output
        int32_t             - zero=success, negative=failure

    \Version 02/21/2014 (jbrookes)
*/
/********************************************************************************F*/
int32_t SocketPacketQueueAdd(SocketPacketQueueT *pPacketQueue, const uint8_t *pPacketData, int32_t iPacketSize, struct sockaddr *pPacketAddr)
{
    SocketPacketQueueEntryT *pQueueEntry;
    // reject if packet data is too large
    if (iPacketSize > SOCKET_MAXUDPRECV)
    {
        NetPrintf(("dirtynet: [%p] packet too large to add to queue\n", pPacketQueue));
        return(-1);
    }
    // if queue is full, overwrite oldest member
    if (pPacketQueue->iNumPackets == pPacketQueue->iMaxPackets)
    {
        NetPrintf(("dirtynet: [%p] add to full queue; oldest entry will be overwritten\n", pPacketQueue));
        pPacketQueue->iPacketHead = (pPacketQueue->iPacketHead + 1) % pPacketQueue->iMaxPackets;
        pPacketQueue->uPacketDrop += 1;
    }
    else
    {
        pPacketQueue->iNumPackets += 1;
        if (pPacketQueue->uPacketMax < (unsigned)pPacketQueue->iNumPackets)
        {
            pPacketQueue->uPacketMax = (unsigned)pPacketQueue->iNumPackets;
        }
    }
    // set packet entry
    NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] [%d] packet queue entry added (%d entries)\n", pPacketQueue, pPacketQueue->iPacketTail, pPacketQueue->iNumPackets));
    pQueueEntry = &pPacketQueue->aPacketQueue[pPacketQueue->iPacketTail];
    ds_memcpy_s(pQueueEntry->aPacketData, sizeof(pQueueEntry->aPacketData), pPacketData, iPacketSize);
    ds_memcpy(&pQueueEntry->PacketAddr, pPacketAddr, sizeof(pQueueEntry->PacketAddr));
    pQueueEntry->iPacketSize = iPacketSize;
    pQueueEntry->uPacketTick = NetTick();
    // add to queue
    pPacketQueue->iPacketTail = (pPacketQueue->iPacketTail + 1) % pPacketQueue->iMaxPackets;
    NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] head=%d tail=%d\n", pPacketQueue, pPacketQueue->iPacketHead, pPacketQueue->iPacketTail));
    // return success
    return(0);
}

/*F********************************************************************************/
/*!
    \Function SocketPacketQueueAlloc

    \Description
        Alloc a packet queue entry.  This is used when receiving data directly into
        the packet queue data buffer.

    \Input *pPacketQueue    - packet queue to alloc entry from

    \Output
        SocketPacketQueueEntryT * - packet queue entry

    \Version 02/24/2014 (jbrookes)
*/
/********************************************************************************F*/
SocketPacketQueueEntryT *SocketPacketQueueAlloc(SocketPacketQueueT *pPacketQueue)
{
    SocketPacketQueueEntryT *pQueueEntry;
    // if queue is full, alloc over oldest member
    if (pPacketQueue->iNumPackets == pPacketQueue->iMaxPackets)
    {
        // print a warning if we're altering a slot that is in use
        if (pPacketQueue->aPacketQueue[pPacketQueue->iPacketTail].iPacketSize != -1)
        {
            NetPrintf(("dirtynet: [%p] alloc to full queue; oldest entry will be overwritten\n", pPacketQueue));
            pPacketQueue->uPacketDrop += 1;
        }
        pPacketQueue->iPacketHead = (pPacketQueue->iPacketHead + 1) % pPacketQueue->iMaxPackets;
    }
    else
    {
        pPacketQueue->iNumPackets += 1;
        if (pPacketQueue->uPacketMax < (unsigned)pPacketQueue->iNumPackets)
        {
            pPacketQueue->uPacketMax = (unsigned)pPacketQueue->iNumPackets;
        }
    }
    // allocate queue entry
    pQueueEntry = &pPacketQueue->aPacketQueue[pPacketQueue->iPacketTail];
    NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] [%d] packet queue entry alloc (%d entries)\n", pPacketQueue, pPacketQueue->iPacketTail, pPacketQueue->iNumPackets));
    // mark packet as allocated
    pQueueEntry->iPacketSize = -1;
    // add to queue
    pPacketQueue->iPacketTail = (pPacketQueue->iPacketTail + 1) % pPacketQueue->iMaxPackets;
    NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] head=%d tail=%d\n", pPacketQueue, pPacketQueue->iPacketHead, pPacketQueue->iPacketTail));
    return(pQueueEntry);
}

/*F********************************************************************************/
/*!
    \Function SocketPacketQueueAllocUndo

    \Description
        Undo a recent call to SocketPacketQueueAlloc().  Cannot be used if packet
        queue has been altered in between calling SocketPacketQueueAlloc() and
        SocketPacketQueueAllocUndo()

    \Input *pPacketQueue    - packet queue to undo alloc entry from

    \Version 09/04/2018 (mclouatre)
*/
/********************************************************************************F*/
void SocketPacketQueueAllocUndo(SocketPacketQueueT *pPacketQueue)
{
    pPacketQueue->iNumPackets -= 1;
    NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] [%d] recent alloc undone from tail (%d entries)\n", pPacketQueue, pPacketQueue->iPacketTail, pPacketQueue->iNumPackets));

    // modular arithmetic is not used here to avoid complications when the substraction underflows
    pPacketQueue->iPacketTail -= 1;
    if (pPacketQueue->iPacketTail < 0)
    {
        pPacketQueue->iPacketTail = pPacketQueue->iMaxPackets - 1;
    }

    NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] head=%d tail=%d\n", pPacketQueue, pPacketQueue->iPacketHead, pPacketQueue->iPacketTail));
}

/*F********************************************************************************/
/*!
    \Function SocketPacketQueueRem

    \Description
        Remove a packet from packet queue

    \Input *pPacketQueue    - packet queue to add to
    \Input *pPacketData     - [out] storage for packet data or NULL
    \Input iPacketSize      - size of packet output data buffer
    \Input *pPacketAddr     - [out] storage for packet addr or NULL

    \Output
        int32_t             - positive=size of packet, zero=no packet, negative=failure

    \Notes
        The packet data and address outputs are optional if you are just
        trying to remove the entry at the head.

    \Version 02/21/2014 (jbrookes)
*/
/********************************************************************************F*/
int32_t SocketPacketQueueRem(SocketPacketQueueT *pPacketQueue, uint8_t *pPacketData, int32_t iPacketSize, struct sockaddr *pPacketAddr)
{
    SocketPacketQueueEntryT *pQueueEntry;
    uint32_t uCurTick = NetTick();

    // nothing to do if queue is empty
    if (pPacketQueue->iNumPackets == 0)
    {
        return(0);
    }
    // get head packet
    pQueueEntry = &pPacketQueue->aPacketQueue[pPacketQueue->iPacketHead];
    // nothing to do if packet was allocated and has not been filled
    if (pQueueEntry->iPacketSize < 0)
    {
        return(0);
    }

    // apply simulated latency, if enabled
    if (pPacketQueue->uLatency != 0)
    {
        // compare to current latency
        if (NetTickDiff(uCurTick, pQueueEntry->uPacketTick) < (signed)pPacketQueue->uLatency + pPacketQueue->iDeviationTime)
        {
            NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] latency=%d, deviation=%d\n", pPacketQueue, NetTickDiff(uCurTick, pPacketQueue->uLatencyTime), pPacketQueue->iDeviationTime));
            return(0);
        }

        // update packet receive timestamp
        SockaddrInSetMisc(&pQueueEntry->PacketAddr, uCurTick);

        // recalculate deviation
        pPacketQueue->iDeviationTime = (signed)NetRand(pPacketQueue->uDeviation*2) - (signed)pPacketQueue->uDeviation;
    }

    // get packet size to copy (will truncate if output buffer is too small)
    if (iPacketSize > pQueueEntry->iPacketSize)
    {
        iPacketSize = pQueueEntry->iPacketSize;
    }
    // copy out packet data and source
    if (pPacketData != NULL)
    {
        ds_memcpy(pPacketData, pQueueEntry->aPacketData, iPacketSize);
    }
    if (pPacketAddr != NULL)
    {
        ds_memcpy(pPacketAddr, &pQueueEntry->PacketAddr, sizeof(pQueueEntry->PacketAddr));
    }
    // remove packet from queue
    pPacketQueue->iNumPackets -= 1;
    NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] [%d] packet queue entry removed from head in %dms (%d entries)\n", pPacketQueue, pPacketQueue->iPacketHead,
        NetTickDiff(NetTick(), pQueueEntry->uPacketTick), pPacketQueue->iNumPackets));
    pPacketQueue->iPacketHead = (pPacketQueue->iPacketHead + 1) % pPacketQueue->iMaxPackets;
    NetPrintfVerbose((DIRTYNET_PACKETQUEUEDEBUG, 0, "dirtynet: [%p] head=%d tail=%d\n", pPacketQueue, pPacketQueue->iPacketHead, pPacketQueue->iPacketTail));

    // simulate packet loss
    if (pPacketQueue->uPacketLoss != 0)
    {
        uint32_t uRand = NetRand(100*65536);
        if (uRand < pPacketQueue->uPacketLoss)
        {
            NetPrintf(("dirtynet: [%p] lost packet (rand=%d, comp=%d)!\n", pPacketQueue, uRand, pPacketQueue->uPacketLoss));
            return(0);
        }
    }

    // return success
    return(pQueueEntry->iPacketSize);
}

/*F********************************************************************************/
/*!
    \Function SocketPacketQueueRemStream

    \Description
        Remove a stream packet from packet queue

    \Input *pPacketQueue    - packet queue to add to
    \Input *pPacketData     - [out] storage for packet data or NULL
    \Input iPacketSize      - size of packet output data buffer

    \Output
        int32_t             - positive=amount of data read, zero=no packet, negative=failure

    \Notes
        The packet data output is optional if you are just trying to remove the entry
        at the head.

    \Version 11/20/2017 (eesponda)
*/
/********************************************************************************F*/
int32_t SocketPacketQueueRemStream(SocketPacketQueueT *pPacketQueue, uint8_t *pPacketData, int32_t iPacketSize)
{
    SocketPacketQueueEntryT *pQueueEntry;
    int32_t iPacketRead;

    // nothing to do if queue is empty
    if (pPacketQueue->iNumPackets == 0)
    {
        return(0);
    }
    // get head packet
    pQueueEntry = &pPacketQueue->aPacketQueue[pPacketQueue->iPacketHead];
    // nothing to do if packet was allocated and has not been filled
    if (pQueueEntry->iPacketSize < 0)
    {
        return(0);
    }

    // get packet size to copy
    iPacketRead = DS_MIN(iPacketSize, pQueueEntry->iPacketSize);

    // copy the packet data and offset by amount read
    if (pPacketData != NULL)
    {
        ds_memcpy(pPacketData, pQueueEntry->aPacketData, iPacketRead);

        pPacketData += iPacketRead;
        iPacketSize -= iPacketRead;
    }

    // if we copied less than the size of the entry adjust the entry as needed, otherwise remove packet from queue
    if ((iPacketRead > 0) && (iPacketRead < pQueueEntry->iPacketSize))
    {
        memmove(pQueueEntry->aPacketData, pQueueEntry->aPacketData+iPacketRead, pQueueEntry->iPacketSize-iPacketRead);
        pQueueEntry->iPacketSize -= iPacketRead;
    }
    else
    {
        pPacketQueue->iNumPackets -= 1;
        pPacketQueue->iPacketHead = (pPacketQueue->iPacketHead + 1) % pPacketQueue->iMaxPackets;
    }

    // continue to copy if we still have space in the buffer
    if (iPacketSize > 0)
    {
        iPacketRead += SocketPacketQueueRemStream(pPacketQueue, pPacketData, iPacketSize);
    }
    return(iPacketRead);
}

/*F********************************************************************************/
/*!
    \Function SocketPacketQueueControl

    \Description
        Control socket packet queue options

    \Input *pPacketQueue    - packet queue control function; different selectors
                              control different behaviors
    \Input iControl         - control selector
    \Input iValue           - selector specific

    \Output
        int32_t             - selector result

    \Notes
        iControl can be one of the following:

        \verbatim
            'pdev' - set simulated packet deviation
            'plat' - set simulated packet latency
            'plos' - set simulated packet loss
        \endverbatim

    \Version 10/07/2014 (jbrookes)
*/
/********************************************************************************F*/
int32_t SocketPacketQueueControl(SocketPacketQueueT *pPacketQueue, int32_t iControl, int32_t iValue)
{
    if (iControl == 'pdev')
    {
        NetPrintf(("dirtynet: setting simulated packet deviation=%dms\n", iValue));
        pPacketQueue->uDeviation = iValue;
        return(0);
    }
    if (iControl == 'plat')
    {
        NetPrintf(("dirtynet: setting simulated packet latency=%dms\n", iValue));
        pPacketQueue->uLatency = iValue;
        return(0);
    }
    if (iControl == 'plos')
    {
        NetPrintf(("dirtynet: setting simulated packet loss to %d.%d\n", iValue >> 16, iValue & 0xffff));
        pPacketQueue->uPacketLoss = iValue;
        return(0);
    }
    return(-1);
}

/*F********************************************************************************/
/*!
    \Function SocketPacketQueueStatus

    \Description
        Get status of socket packet queue

    \Input *pPacketQueue    - packet queue to get status of
    \Input iStatus          - status selector

    \Output
        int32_t             - selector result

    \Notes
        iStatus can be one of the following:

        \verbatim
            'pful' - TRUE if queue is full, FALSE otherwise
            'pdrp' - number of packets overwritten due to queue overflow
            'pmax' - maximum number of packets in queue (high water)
            'psiz' - queue size (in packets)
        \endverbatim

    \Version 10/20/2015 (jbrookes)
*/
/********************************************************************************F*/
int32_t SocketPacketQueueStatus(SocketPacketQueueT *pPacketQueue, int32_t iStatus)
{
    if (iStatus == 'pful')
    {
        return(pPacketQueue->iMaxPackets == pPacketQueue->iNumPackets ? TRUE : FALSE);
    }
    if (iStatus == 'pdrp')
    {
        return((signed)pPacketQueue->uPacketDrop);
    }
    if (iStatus == 'pmax')
    {
        return((signed)pPacketQueue->uPacketMax);
    }
    if (iStatus == 'psiz')
    {
        return(pPacketQueue->iMaxPackets);
    }
    // invalid selector
    return(-1);
}

/*
    Rate functions
*/

/*F********************************************************************************/
/*!
    \Function SocketRateUpdate

    \Description
        Update socket data rate estimation

    \Input *pRate           - state used to store rate estimation data
    \Input iData            - amount of data being sent/recv
    \Input *pOpName         - indicates send or recv (for debug use only)

    \Notes
        Rate estimation is based on a rolling 16-deep history of ~100ms samples
        of data rate (the actual period may vary slightly based on update rate
        and tick resolution) covering a total of ~1600-1700ms.  We also keep track
        of the rate we are called at (excluding multiple calls within the same
        tick) so we can estimate how much data we need to have sent by the next
        time we are called.  This is important because we will always be shooting
        lower than our cap if we don't consider this factor, and the amount we
        are shooting lower by increases the slower the update rate.

    \Version 08/20/2014 (jbrookes)
*/
/********************************************************************************F*/
void SocketRateUpdate(SocketRateT *pRate, int32_t iData, const char *pOpName)
{
    const uint32_t uMaxIndex = (unsigned)(sizeof(pRate->aDataHist)/sizeof(pRate->aDataHist[0]));
    uint32_t uCurTick = NetTick(), uOldTick;
    uint32_t uIndex, uCallRate, uCallSum, uDataSum;
    int32_t iTickDiff;

    // reserve tick=0 as uninitialized
    if (uCurTick == 0)
    {
        uCurTick = 1;
    }
    // initialize update tick counters
    if (pRate->uLastTick == 0)
    {
        pRate->uLastTick = NetTickDiff(uCurTick, 2);
        // initialize rate tick counter to current-100 so as to force immediate history update
        pRate->uLastRateTick = NetTickDiff(uCurTick, 100);
        // start off at max rate
        pRate->uCurRate = pRate->uNextRate = pRate->uMaxRate;
    }
    // exclude error results
    if (iData < 0)
    {
        return;
    }

    // update the data
    pRate->aDataHist[pRate->uDataIndex] += iData;
    /* update the call count, only only if time has elapsed since our last call, since we
       want the true update rate) */
    if (NetTickDiff(uCurTick, pRate->uLastTick) > 1)
    {
        pRate->aCallHist[pRate->uDataIndex] += 1;
    }
    // update last update tick
    pRate->uLastTick = uCurTick;
    /* update timestamp, but only if it hasn't been updated already.  we do this so we can
       correctly update the rate calculation continuously with the current sample */
    if (pRate->aTickHist[pRate->uDataIndex] == 0)
    {
        pRate->aTickHist[pRate->uDataIndex] = uCurTick;
    }

    // get oldest tick & sum recorded data & callcounts
    for (uIndex = (pRate->uDataIndex + 1) % uMaxIndex, uOldTick = 0, uCallSum = 0, uDataSum = 0; ; uIndex = (uIndex + 1) % uMaxIndex)
    {
        // skip uninitialized tick values
        if ((uOldTick == 0) && (pRate->aTickHist[uIndex] != 0))
        {
            uOldTick = pRate->aTickHist[uIndex];
        }
        // update call sum
        uCallSum += pRate->aCallHist[uIndex];
        // update data sum
        uDataSum += pRate->aDataHist[uIndex];
        // quit when we've hit every entry
        if (uIndex == pRate->uDataIndex)
        {
            break;
        }
    }

    // update rate estimation
    if ((iTickDiff = NetTickDiff(uCurTick, uOldTick)) > 0)
    {
        // calculate call rate
        uCallRate = (uCallSum > 0) ? iTickDiff/uCallSum : 0;
        // update current rate estimation
        pRate->uCurRate = (uDataSum * 1000) / iTickDiff;
        // update next rate estimation (fudge slightly by 2x as it gives us better tracking to our desired rate)
        pRate->uNextRate = (uDataSum * 1000) / (iTickDiff+(uCallRate*2));

        #if DIRTYNET_RATEDEBUG
        if (pRate->uCurRate != 0)
        {
            NetPrintf(("dirtynet: [%p] rate=%4.2fkb nextrate=%4.2f callrate=%dms tickdiff=%d tick=%d\n", pRate, ((float)pRate->uCurRate)/1024.0f, ((float)pRate->uNextRate)/1024.0f, uCallRate, iTickDiff, uCurTick));
        }
        #endif
    }

    // move to next slot in history every 100ms
    if (NetTickDiff(uCurTick, pRate->uLastRateTick) >= 100)
    {
        pRate->uDataIndex = (pRate->uDataIndex + 1) % uMaxIndex;
        pRate->aDataHist[pRate->uDataIndex] = 0;
        pRate->aTickHist[pRate->uDataIndex] = 0;
        pRate->aCallHist[pRate->uDataIndex] = 0;
        pRate->uLastRateTick = uCurTick;

        #if DIRTYNET_RATEDEBUG
        if (pRate->uCurRate != 0)
        {
            NetPrintf(("dirtynet: [%p] %s=%5d rate=%4.2fkb/s indx=%2d tick=%08x diff=%d\n", pRate, pOpName, uDataSum, ((float)pRate->uCurRate)/1024.0f, pRate->uDataIndex, uCurTick, iTickDiff));
        }
        #endif
    }
}

/*F********************************************************************************/
/*!
    \Function SocketRateThrottle

    \Description
        Throttles data size to send or recv based on calculated data rate and
        configured max rate.

    \Input *pRate           - state used to calculate rate
    \Input iSockType        - socket type (SOCK_DGRAM, SOCK_STREAM, etc)
    \Input iData            - amount of data being sent/recv
    \Input *pOpName         - indicates send or recv (for debug use only)

    \Output
        int32_t             - amount of data to send/recv

    \Version 08/20/2014 (jbrookes)
*/
/********************************************************************************F*/
int32_t SocketRateThrottle(SocketRateT *pRate, int32_t iSockType, int32_t iData, const char *pOpName)
{
    int32_t iRateDiff;

    // max rate of zero means rate throttling is disabled
    if ((pRate->uMaxRate == 0) || (iSockType != SOCK_STREAM))
    {
        return(iData);
    }

    // enforce min max update rate
    if (pRate->uMaxRate < DIRTYNET_MIN_THROTTLE_RATE)
    {
        NetPrintf(("dirtynet: [%p] clamping max rate throttle of %d to %d", pRate->uMaxRate, DIRTYNET_MIN_THROTTLE_RATE));
        pRate->uMaxRate = DIRTYNET_MIN_THROTTLE_RATE;
    }

    // if we're exceeding the max rate, update rate estimation and return no data
    if ((iRateDiff = pRate->uMaxRate - pRate->uNextRate) <= 0)
    {
        NetPrintfVerbose((DIRTYNET_RATEDEBUG, 0, "dirtynet: [%p] exceeding max rate; clamping to zero\n", pRate));
        iData = 0;
    }
    else if (iData > iRateDiff) // return a limited amount of data so as not to exceed max rate
    {
        /* clamp in multiples of the typical TCP maximum segment size, so as not to generate fragmented packets
           note that the TCP maximum segment size is equal to our minimum throttle rate, otherwise setting the
           uMaxRate to less than this value would result in iData always being equal to 0. */
        iData = (iRateDiff / DIRTYNET_MIN_THROTTLE_RATE) * DIRTYNET_MIN_THROTTLE_RATE;
        NetPrintfVerbose((DIRTYNET_RATEDEBUG, 0, "dirtynet: [%p] exceeding max rate; clamping to %d bytes from %d bytes\n", pRate, iRateDiff, pRate->uMaxRate - pRate->uNextRate));
    }

    // if we are returning no data, update the rate as the caller won't
    if (iData == 0)
    {
        SocketRateUpdate(pRate, 0, pOpName);
    }

    return(iData);
}

/*
    Send Callback functions
*/

/*F********************************************************************************/
/*!
    \Function SocketSendCallbackAdd

    \Description
        Register a new socket send callback

    \Input aCbEntries   - collection of callbacks to add to
    \Input *pCbEntry    - entry to be added

    \Output
        int32_t         - zero=success; negative=failure

    \Version 07/18/2014 (mclouatre)
*/
/********************************************************************************F*/
int32_t SocketSendCallbackAdd(SocketSendCallbackEntryT aCbEntries[], SocketSendCallbackEntryT *pCbEntry)
{
    int32_t iRetCode = -1; // default to failure
    int32_t iEntryIndex;

    for(iEntryIndex = 0; iEntryIndex < SOCKET_MAXSENDCALLBACKS; iEntryIndex++)
    {
        if (aCbEntries[iEntryIndex].pSendCallback == NULL)
        {
            aCbEntries[iEntryIndex].pSendCallback = pCbEntry->pSendCallback;
            aCbEntries[iEntryIndex].pSendCallref = pCbEntry->pSendCallref;

            NetPrintf(("dirtynet: adding send callback (%p, %p)\n", pCbEntry->pSendCallback, pCbEntry->pSendCallref));

            iRetCode = 0;  // success

            break;
        }
    }

    return(iRetCode);
}

/*F********************************************************************************/
/*!
    \Function SocketSendCallbackRem

    \Description
        Unregister a socket send callback that was already registered.

    \Input aCbEntries   - collection of callbacks to remove from
    \Input *pCbEntry    - entry to be removed

    \Output
        int32_t         - zero=success; negative=failure

    \Version 07/18/2014 (mclouatre)
*/
/********************************************************************************F*/
int32_t SocketSendCallbackRem(SocketSendCallbackEntryT aCbEntries[], SocketSendCallbackEntryT *pCbEntry)
{
    int32_t iRetCode = -1; // default to failure
    int32_t iEntryIndex;

    for(iEntryIndex = 0; iEntryIndex < SOCKET_MAXSENDCALLBACKS; iEntryIndex++)
    {
        if (aCbEntries[iEntryIndex].pSendCallback == pCbEntry->pSendCallback && aCbEntries[iEntryIndex].pSendCallref == pCbEntry->pSendCallref)
        {
            NetPrintf(("dirtynet: removing send callback (%p, %p)\n", aCbEntries[iEntryIndex].pSendCallback,  aCbEntries[iEntryIndex].pSendCallref));

            aCbEntries[iEntryIndex].pSendCallback = NULL;
            aCbEntries[iEntryIndex].pSendCallref = NULL;

            iRetCode = 0;  // success

            break;
        }
    }

    return(iRetCode);
}

/*F********************************************************************************/
/*!
    \Function SocketSendCallbackInvoke

    \Description
        Invoke all send callbacks in specified collection. Only one of the callback
        is supposed to return "handled"... warn if not the case.

    \Input aCbEntries   - collection of callbacks to be invoked
    \Input pSocket      - socket reference
    \Input iType        - socket type
    \Input *pBuf        - the data to be sent
    \Input iLen         - size of data
    \Input *pTo         - the address to send to (NULL=use connection address)

    \Output
        int32_t         - 0 = send not handled; >0 = send successfully handled (bytes sent); <0 = send handled but failed (SOCKERR_XXX)

    \Version 07/18/2014 (mclouatre)
*/
/********************************************************************************F*/
int32_t SocketSendCallbackInvoke(SocketSendCallbackEntryT aCbEntries[], SocketT *pSocket, int32_t iType, const char *pBuf, int32_t iLen, const struct sockaddr *pTo)
{
    int32_t iRetCode = 0;  // default to send not handled
    int32_t iResult;
    int32_t iEntryIndex;

    for (iEntryIndex = 0; iEntryIndex < SOCKET_MAXSENDCALLBACKS; iEntryIndex++)
    {
        // we expect zero or one send callback to handle the send, more than one indicates an invalid condition
        if (aCbEntries[iEntryIndex].pSendCallback != NULL)
        {
            if((iResult = aCbEntries[iEntryIndex].pSendCallback(pSocket, iType, (const uint8_t *)pBuf, iLen, pTo, aCbEntries[iEntryIndex].pSendCallref)) != 0)
            {
                if (iRetCode == 0)
                {
                    iRetCode = iResult;
                }
                else
                {
                    NetPrintf(("dirtynet: critical error - send handled by more than one callback  (iEntryIndex = %d, iResult = %d)\n", iEntryIndex, iResult));
                }
            }
        }
    }

    return(iRetCode);
}

/*

    Addr map functions

*/
#ifndef DIRTYCODE_NX
/*F*************************************************************************************/
/*!
    \Function _Sockaddr6SetAddrV4Mapped

    \Description
        Set a V4-mapped IPv6 in6_addr

    \Input *pAddr6      - [out] storage for IPv4-mapped IPv6 address
    \Input uAddr4       - IPv4 address to map

    \Version 03/01/2016 (jbrookes)
*/
/************************************************************************************F*/
static void _Sockaddr6SetAddrV4Mapped(struct in6_addr *pAddr6, uint32_t uAddr4)
{
    ds_memclr(pAddr6, sizeof(*pAddr6));
    pAddr6->s6_addr[10] = 0xff;
    pAddr6->s6_addr[11] = 0xff;
    pAddr6->s6_addr[12] = (uint8_t)(uAddr4 >> 24);
    pAddr6->s6_addr[13] = (uint8_t)(uAddr4 >> 16);
    pAddr6->s6_addr[14] = (uint8_t)(uAddr4 >> 8);
    pAddr6->s6_addr[15] = (uint8_t)(uAddr4);
}

/*F*************************************************************************************/
/*!
    \Function _Sockaddr6SetV4Mapped

    \Description
        Set a V4-mapped IPv6 sockaddr6

    \Input *pResult6    - [out] storage for IPv4-mapped IPv6 sockaddr
    \Input *pSource4    - IPv4 address to map

    \Version 03/01/2016 (jbrookes)
*/
/************************************************************************************F*/
static void _Sockaddr6SetV4Mapped(struct sockaddr_in6 *pResult6, const struct sockaddr *pSource4)
{
    pResult6->sin6_family = AF_INET6;
    pResult6->sin6_port = SocketNtohs(SockaddrInGetPort(pSource4));
    pResult6->sin6_flowinfo = 0;
    _Sockaddr6SetAddrV4Mapped(&pResult6->sin6_addr, SockaddrInGetAddr(pSource4));
    pResult6->sin6_scope_id = 0;
}

/*F*************************************************************************************/
/*!
    \Function _SocketAddrMapAlloc

    \Description
        Alloc (or re-alloc) address map entry list

    \Input *pAddrMap            - address map
    \Input iNumEntries          - new entry list size
    \Input iMemGroup            - memory group
    \Input *pMemGroupUserData   - memory group user data

    \Output
        int32_t         - negative=error, zero=success

    \Version 04/17/2013 (jbrookes)
*/
/************************************************************************************F*/
static int32_t _SocketAddrMapAlloc(SocketAddrMapT *pAddrMap, int32_t iNumEntries, int32_t iMemGroup, void *pMemGroupUserData)
{
    int32_t iOldEntryListSize = 0, iNewEntryListSize = iNumEntries*sizeof(SocketAddrMapEntryT);
    SocketAddrMapEntryT *pNewEntries;

    // allocate new map memory
    if ((pNewEntries = (SocketAddrMapEntryT *)DirtyMemAlloc(iNewEntryListSize, SOCKET_MEMID, iMemGroup, pMemGroupUserData)) == NULL)
    {
        return(-1);
    }
    // clear new map memory
    ds_memclr(pNewEntries, iNewEntryListSize);
    // copy previous map data
    if (pAddrMap->pMapEntries != NULL)
    {
        iOldEntryListSize = pAddrMap->iNumEntries*sizeof(SocketAddrMapEntryT);
        ds_memcpy(pNewEntries, pAddrMap->pMapEntries, iOldEntryListSize);
        DirtyMemFree(pAddrMap->pMapEntries, SOCKET_MEMID, iMemGroup, pMemGroupUserData);
    }
    // update state
    pAddrMap->iNumEntries = iNumEntries;
    pAddrMap->pMapEntries = pNewEntries;
    pAddrMap->iMemGroup = iMemGroup;
    pAddrMap->pMemGroupUserData = pMemGroupUserData;
    // return success
    return(0);
}

/*F*************************************************************************************/
/*!
    \Function _SocketAddrMapGet

    \Description
        Find a map entry, based on specified address

    \Input *pAddrMap    - address map
    \Input *pAddr       - address to get entry for
    \Input iAddrSize    - size of address

    \Output
        SockAddrMapEntryT * - pointer to map entry or NULL if not found

    \Version 04/17/2013 (jbrookes)
*/
/************************************************************************************F*/
static SocketAddrMapEntryT *_SocketAddrMapGet(const SocketAddrMapT *pAddrMap, const struct sockaddr *pAddr, int32_t iAddrSize)
{
    const struct sockaddr_in6 *pAddr6 = (const struct sockaddr_in6 *)pAddr;
    SocketAddrMapEntryT *pMapEntry;
    int32_t iMapEntry;

    for (iMapEntry = 0; iMapEntry < pAddrMap->iNumEntries; iMapEntry += 1)
    {
        pMapEntry = &pAddrMap->pMapEntries[iMapEntry];
        if ((pAddr->sa_family == AF_INET) && (pMapEntry->iVirtualAddress == SockaddrInGetAddr(pAddr)))
        {
            return(pMapEntry);
        }
        if ((pAddr->sa_family == AF_INET6) && (!memcmp(&pAddr6->sin6_addr, &pMapEntry->SockAddr6.sin6_addr, sizeof(pAddr6->sin6_addr))))
        {
            return(pMapEntry);
        }
    }
    return(NULL);
}

/*F*************************************************************************************/
/*!
    \Function _SocketAddrMapSet

    \Description
        Initialize a map entry

    \Input *pAddrMap    - address map
    \Input *pMapEntry   - entry to set
    \Input *pAddr6      - IPv6 address to set
    \Input iAddrSize    - size of address

    \Version 04/17/2013 (jbrookes)
*/
/************************************************************************************F*/
static void _SocketAddrMapSet(SocketAddrMapT *pAddrMap, SocketAddrMapEntryT *pMapEntry, const struct sockaddr_in6 *pAddr6, int32_t iAddrSize)
{
    pMapEntry->iRefCount = 1;
    pMapEntry->iVirtualAddress = pAddrMap->iNextVirtAddr;
    pAddrMap->iNextVirtAddr = (pAddrMap->iNextVirtAddr + 1) & 0x00ffffff;
    ds_memcpy(&pMapEntry->SockAddr6, pAddr6, iAddrSize);
    NetPrintfVerbose((SocketInfo(NULL, 'spam', 0, NULL, 0), 1, "dirtynet: [%d] add map %A to %a\n", pMapEntry - pAddrMap->pMapEntries, pAddr6, pMapEntry->iVirtualAddress));
}

/*F*************************************************************************************/
/*!
    \Function _SocketAddrMapRemap

    \Description
        Alter the value of an initialized map entry

    \Input *pAddrMap    - address map
    \Input *pMapEntry   - entry to set
    \Input *pAddr6      - IPv6 address to set
    \Input iAddrSize    - size of address

    \Version 12/03/2015 (amakoukji)
*/
/************************************************************************************F*/
static void _SocketAddrMapRemap(const SocketAddrMapT *pAddrMap, SocketAddrMapEntryT *pMapEntry, const struct sockaddr_in6 *pAddr6, int32_t iAddrSize)
{
    if (memcmp(&pMapEntry->SockAddr6, pAddr6, iAddrSize) == 0)
    {
        NetPrintf(("dirtynet: [%d] attempt to remap %a to identical IPv6 address %A\n", pMapEntry - pAddrMap->pMapEntries, pMapEntry->iVirtualAddress, pAddr6));
    }
    NetPrintfVerbose((SocketInfo(NULL, 'spam', 0, NULL, 0), 1, "dirtynet: [%d] remapped %A -> %A for virtual address %a\n", pMapEntry - pAddrMap->pMapEntries, &pMapEntry->SockAddr6, pAddr6, pMapEntry->iVirtualAddress));
    ds_memcpy(&pMapEntry->SockAddr6, pAddr6, iAddrSize);
}

/*F*************************************************************************************/
/*!
    \Function _SocketAddrMapDel

    \Description
        Dereference (and clear, if no more references) a map entry

    \Input *pAddrMap    - address map
    \Input *pMapEntry   - entry to del

    \Version 04/17/2013 (jbrookes)
*/
/************************************************************************************F*/
static void _SocketAddrMapDel(SocketAddrMapT *pAddrMap, SocketAddrMapEntryT *pMapEntry)
{
    NetPrintfVerbose((SocketInfo(NULL, 'spam', 0, NULL, 0), 1, "dirtynet: [%d] del map %A to %a (decremented refcount to %d)\n",
        pMapEntry - pAddrMap->pMapEntries, &pMapEntry->SockAddr6, pMapEntry->iVirtualAddress, pMapEntry->iRefCount-1));
    if (--pMapEntry->iRefCount == 0)
    {
        ds_memclr(pMapEntry, sizeof(*pMapEntry));
    }
}

/*F*************************************************************************************/
/*!
    \Function _SocketAddrMapAdd

    \Description
        Add an IPv6 address to the address mapping table

    \Input *pAddrMap    - address map
    \Input *pAddr6      - address to add to the mapping table
    \Input iAddrSize    - size of address

    \Output
        int32_t         - SOCKMAP_ERROR=error, else virtual IPv4 address for newly mapped IPv6 address

    \Version 04/17/2013 (jbrookes)
*/
/************************************************************************************F*/
static int32_t _SocketAddrMapAdd(SocketAddrMapT *pAddrMap, const struct sockaddr_in6 *pAddr6, int32_t iAddrSize)
{
    int32_t iMapEntry;

    // find an empty slot
    for (iMapEntry = 0; iMapEntry < pAddrMap->iNumEntries; iMapEntry += 1)
    {
        if (pAddrMap->pMapEntries[iMapEntry].iVirtualAddress == 0)
        {
            _SocketAddrMapSet(pAddrMap, &pAddrMap->pMapEntries[iMapEntry], pAddr6, iAddrSize);
            return(pAddrMap->pMapEntries[iMapEntry].iVirtualAddress);
        }
    }

    // if no empty slot, realloc the array
    if ((_SocketAddrMapAlloc(pAddrMap, pAddrMap->iNumEntries+8, pAddrMap->iMemGroup, pAddrMap->pMemGroupUserData)) < 0)
    {
        return(SOCKMAP_ERROR);
    }
    // try the add again
    return(_SocketAddrMapAdd(pAddrMap, pAddr6, iAddrSize));
}

/*F*************************************************************************************/
/*!
    \Function SocketAddrMapInit

    \Description
        Initialize a Socket Address Map

    \Input *pAddrMap    - address map
    \Input iMemGroup    - memory group
    \Input *pMemGroupUserData - memory group user data

    \Version 03/03/2016 (jbrookes)
*/
/************************************************************************************F*/
void SocketAddrMapInit(SocketAddrMapT *pAddrMap, int32_t iMemGroup, void *pMemGroupUserData)
{
    // set up address map
    ds_memclr(pAddrMap, sizeof(*pAddrMap));
    pAddrMap->iMemGroup = iMemGroup;
    pAddrMap->pMemGroupUserData = pMemGroupUserData;
    // init ipv6 map virtual address
    pAddrMap->iNextVirtAddr = NetTick() & 0x00ffffff;
}

/*F*************************************************************************************/
/*!
    \Function SocketAddrMapShutdown

    \Description
        Clean up a Socket Address Map

    \Input *pAddrMap    - address map

    \Version 03/03/2016 (jbrookes)
*/
/************************************************************************************F*/
void SocketAddrMapShutdown(SocketAddrMapT *pAddrMap)
{
    if (pAddrMap->pMapEntries != NULL)
    {
        DirtyMemFree(pAddrMap->pMapEntries, SOCKET_MEMID, pAddrMap->iMemGroup, pAddrMap->pMemGroupUserData);
    }
}

/*F*************************************************************************************/
/*!
    \Function SocketAddrMapAddress

    \Description
        Map an IPv6 address and return a virtual IPv4 address that can be used to
        reference it.

    \Input *pAddrMap    - address map
    \Input *pAddr       - address to add to the mapping table
    \Input iAddrSize    - size of address

    \Output
        int32_t         - SOCKMAP_ERROR=error, else virtual IPv4 address for newly mapped IPv6 address

    \Version 04/17/2013 (jbrookes)
*/
/************************************************************************************F*/
int32_t SocketAddrMapAddress(SocketAddrMapT *pAddrMap, const struct sockaddr *pAddr, int32_t iAddrSize)
{
    const struct sockaddr_in6 *pAddr6 = (const struct sockaddr_in6 *)pAddr;
    SocketAddrMapEntryT *pMapEntry;

    // if IPv4, just return the IPv4 address directly, without mapping
    if (pAddr->sa_family == AF_INET)
    {
        return(SockaddrInGetAddr(pAddr));
    }
    // we only map ipv6 addresses
    if ((pAddr->sa_family != AF_INET6) || (iAddrSize < (signed)sizeof(struct sockaddr_in6)))
    {
        NetPrintf(("dirtynet: can't map address of type %d size=%d\n", pAddr->sa_family, iAddrSize));
        return(SOCKMAP_ERROR);
    }
    // force address size to in6 (is this necessary??)
    iAddrSize = sizeof(struct sockaddr_in6);
    // if it's an IPv4-mapped IPv6 address, return the IPv4 address
    if (_SockaddrIn6IsIPv4(pAddr6) || _SockaddrIn6IsZero(pAddr6))
    {
        return(SockaddrIn6GetAddr4(pAddr6));
    }
    // see if this address is already mapped
    if ((pMapEntry = _SocketAddrMapGet(pAddrMap, pAddr, iAddrSize)) != NULL)
    {
        pMapEntry->iRefCount += 1;
        NetPrintfVerbose((SocketInfo(NULL, 'spam', 0, NULL, 0), 1, "dirtynet: [%d] map %A to %a (incremented refcount to %d)\n", pMapEntry - pAddrMap->pMapEntries, &pMapEntry->SockAddr6, pMapEntry->iVirtualAddress, pMapEntry->iRefCount));
        return(pMapEntry->iVirtualAddress);
    }
    // add it to the map
    return(_SocketAddrMapAdd(pAddrMap, pAddr6, iAddrSize));
}

/*F*************************************************************************************/
/*!
    \Function SocketAddrRemapAddress

    \Description
        Remap an existing IPv6 address and return a virtual IPv4 address that can be used to
        reference it.

    \Input *pAddrMap    - address map
    \Input *pOldAddr    - address that should be in mapping table
    \Input *pNewAddr    - address to update to the mapping table
    \Input iAddrSize    - size of address

    \Output
        int32_t         - SOCKMAP_ERROR=error, else virtual IPv4 address for remapped IPv6 address

    \Version 12/03/2015 (amakoukji)
*/
/************************************************************************************F*/
int32_t SocketAddrRemapAddress(SocketAddrMapT *pAddrMap, const struct sockaddr *pOldAddr, const struct sockaddr *pNewAddr, int32_t iAddrSize)
{
    SocketAddrMapEntryT *pMapEntry;

    // if IPv4, just return the IPv4 address directly, without mapping
    if (pNewAddr->sa_family == AF_INET)
    {
        return(SockaddrInGetAddr(pNewAddr));
    }
    // we only map ipv6 addresses
    if ((pNewAddr->sa_family != AF_INET6) || (iAddrSize < (signed)sizeof(struct sockaddr_in6)))
    {
        NetPrintf(("dirtynet: can't remap address of type %d size=%d\n", pNewAddr->sa_family, iAddrSize));
        return(SOCKMAP_ERROR);
    }
    iAddrSize = sizeof(struct sockaddr_in6);
    // see if this address is already mapped
    if ((pMapEntry = _SocketAddrMapGet(pAddrMap, (const struct sockaddr *)pOldAddr, iAddrSize)) != NULL)
    {
        NetPrintf(("dirtynet: attempting to remap virtual address %a, from %A to %A\n", pMapEntry->iVirtualAddress, pOldAddr, pNewAddr));
        _SocketAddrMapRemap(pAddrMap, pMapEntry, (const struct sockaddr_in6 *)pNewAddr, iAddrSize);
        return(pMapEntry->iVirtualAddress);
    }

    // did not find the address
    NetPrintf(("dirtynet: attempt to remap unmapped address %A, attempting to add mapping instead\n", pNewAddr));
    return(SocketAddrMapAddress(pAddrMap, (const struct sockaddr *)pNewAddr, iAddrSize));
}

/*F*************************************************************************************/
/*!
    \Function SocketAddrUnmapAddress

    \Description
        Removes an address mapping from the mapping table.

    \Input *pAddrMap    - address map
    \Input *pAddr       - address to remove from the mapping table
    \Input iAddrSize    - size of address

    \Output
        int32_t         - negative=error, else zero

    \Version 04/18/2013 (jbrookes)
*/
/************************************************************************************F*/
int32_t SocketAddrUnmapAddress(SocketAddrMapT *pAddrMap, const struct sockaddr *pAddr, int32_t iAddrSize)
{
    SocketAddrMapEntryT *pMapEntry;

    // we only map ipv6 addresses
    if ((pAddr->sa_family != AF_INET6) || (iAddrSize < (signed)sizeof(struct sockaddr_in6)))
    {
        NetPrintf(("dirtynet: can't unmap address of type %d size=%d\n", pAddr->sa_family, iAddrSize));
        return(-1);
    }
    iAddrSize = sizeof(struct sockaddr_in6);
    // get the map entry for this address
    if ((pMapEntry = _SocketAddrMapGet(pAddrMap, (const struct sockaddr *)pAddr, iAddrSize)) == NULL)
    {
        NetPrintf(("dirtynet: address unmap operation on an address not in the table\n"));
        return(-2);
    }
    // unmap it
    _SocketAddrMapDel(pAddrMap, pMapEntry);
    return(0);
}

/*F*************************************************************************************/
/*!
    \Function SocketAddrMapGet

    \Description
        Check if an address is in the address map, and if so return its mapped equivalent

    \Input *pAddrMap    - address map
    \Input *pResult     - [out] storage for result
    \Input *pSource     - source address
    \Input *pNameLen    - [out] storage for result length

    \Output
        struct sockaddr * - pointer to result address, or NULL if not found

    \Version 04/18/2013 (jbrookes)
*/
/************************************************************************************F*/
struct sockaddr *SocketAddrMapGet(const SocketAddrMapT *pAddrMap, struct sockaddr *pResult, const struct sockaddr *pSource, int32_t *pNameLen)
{
    SocketAddrMapEntryT *pMapEntry = _SocketAddrMapGet(pAddrMap, pSource, *pNameLen);
    struct sockaddr *pReturn = NULL;

    if (pMapEntry != NULL)
    {
        if (pResult->sa_family == AF_INET)
        {
            SockaddrInit(pResult, AF_INET);
            SockaddrInSetAddr(pResult, pMapEntry->iVirtualAddress);
            pReturn = pResult;
        }
        else if (pResult->sa_family == AF_INET6)
        {
            ds_memcpy_s(pResult, sizeof(*pResult), &pMapEntry->SockAddr6, sizeof(pMapEntry->SockAddr6));
            pReturn = pResult;
        }
    }

    return(pReturn);
}

/*F*************************************************************************************/
/*!
    \Function SocketAddrMapTranslate

    \Description
        Translate the following:
        - IPv4 real address to IPV4-mapped IPv6 address
        - IPv4 virtual address to IPv6 address
        - IPv6 address to IPv4 address

    \Input *pAddrMap    - address map
    \Input *pResult     - [out] storage for translated result
    \Input *pSource     - source address
    \Input *pNameLen    - [out] storage for result length

    \Output
        struct sockaddr * - pointer to resulting IPv6 address

    \Version 04/15/2013 (jbrookes)
*/
/************************************************************************************F*/
struct sockaddr *SocketAddrMapTranslate(const SocketAddrMapT *pAddrMap, struct sockaddr *pResult, const struct sockaddr *pSource, int32_t *pNameLen)
{
    SocketAddrMapEntryT *pMapEntry;
    struct sockaddr *pReturn = NULL;

    // handle broken use cases where the family is not specified
    if ((pSource->sa_family != AF_INET) && (pSource->sa_family != AF_INET6))
    {
        NetPrintf(("dirtynet: unsupported source family %d in SocketAddrMapTranslate(); assuming AF_INET\n", pSource->sa_family));
        ((struct sockaddr *)pSource)->sa_family = AF_INET;
    }
    if ((pResult->sa_family != AF_INET) && (pResult->sa_family != AF_INET6))
    {
        NetPrintf(("dirtynet: unsupported result family %d in SocketAddrMapTranslate(); assuming AF_INET\n", pResult->sa_family));
        pResult->sa_family = AF_INET;
    }

    // handle IPv4->IPv6
    if ((pSource->sa_family == AF_INET) && (pResult->sa_family == AF_INET6))
    {
        struct sockaddr_in6 *pResult6 = (struct sockaddr_in6 *)pResult;
        uint32_t uAddr = SockaddrInGetAddr(pSource);
        // handle a regular IPv4 address
        if ((uAddr == 0) || ((uAddr >> 24) != 0))
        {
            ds_memclr(pResult, sizeof(*pResult));
            _Sockaddr6SetV4Mapped(pResult6, pSource);
            *pNameLen = sizeof(*pResult6);
            pReturn = pResult;
        }
        // get IPv6 address from virtual IPv4
        else if ((pMapEntry = _SocketAddrMapGet(pAddrMap, pSource, *pNameLen)) != NULL)
        {
            ds_memcpy_s(pResult6, sizeof(*pResult6), &pMapEntry->SockAddr6, sizeof(pMapEntry->SockAddr6));
            pResult6->sin6_port = SocketNtohs(SockaddrInGetPort(pSource));
            *pNameLen = sizeof(*pResult6);
            pReturn = pResult;
        }
        else
        {
            NetPrintf(("dirtynet: could not find address for virtual address %a\n", SockaddrInGetAddr(pSource)));
        }
    }
    // handle IPv6->IPv4
    else if ((pSource->sa_family == AF_INET6) && (pResult->sa_family == AF_INET))
    {
        struct sockaddr_in6 *pSource6 = (struct sockaddr_in6 *)pSource;
        // translate IPv6 to virtual IPv4 address
        if ((pMapEntry = _SocketAddrMapGet(pAddrMap, pSource, *pNameLen)) != NULL)
        {
            struct sockaddr *pResult4 = (struct sockaddr *)pResult;
            SockaddrInit(pResult4, AF_INET);
            SockaddrInSetAddr(pResult4, pMapEntry->iVirtualAddress);
            SockaddrInSetPort(pResult4, SocketHtons(pSource6->sin6_port));
            *pNameLen = sizeof(*pResult4);
            pReturn = pResult;
        }
        // is this an IPv4-mapped IPv6 address?
        else if (_SockaddrIn6IsIPv4(pSource6))
        {
            struct sockaddr *pResult4 = (struct sockaddr *)pResult;
            uint32_t uAddress = SockaddrIn6GetAddr4(pSource6);
            SockaddrInit(pResult4, AF_INET);
            SockaddrInSetAddr(pResult4, uAddress);
            SockaddrInSetPort(pResult4, SocketHtons(pSource6->sin6_port));
            *pNameLen = sizeof(*pResult4);
            pReturn = pResult;
        }
    }
    else
    {
        *pNameLen = (pResult->sa_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
        ds_memcpy_s(pResult, *pNameLen, pSource, *pNameLen);
        pReturn = pResult;
    }
    if (pReturn != NULL)
    {
        NetPrintfVerbose((SocketInfo(NULL, 'spam', 0, NULL, 0), 2, "dirtynet: map translate %A->%A\n", pSource, pReturn));
    }
    else
    {
        NetPrintf(("dirtynet: map translate %A failed\n", pSource));
        pReturn = (struct sockaddr *)pSource;
        *pNameLen = sizeof(*pSource);
    }
    return(pReturn);
}
#endif
