Kawe Mazidjatari b3a68ed095 Add EABase, EAThread and DirtySDK to R5sdk
DirtySDK (EA's Dirty Sockets library) will be used for the LiveAPI implementation, and depends on: EABase, EAThread.
2024-04-05 18:29:03 +02:00

2666 lines
90 KiB
C

/*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
} 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
} 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);
}
/*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;
// 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;
// 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);
}
// 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);
}
// 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)
{
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);
// 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*/
int32_t CommUDPSend(CommUDPRef *pRef, const void *pBuffer, int32_t iLength, uint32_t uFlags)
{
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
{
// 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*/
int32_t CommUDPPeek(CommUDPRef *pRef, void *pTarget, int32_t iLength, uint32_t *pWhen)
{
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;
// 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*/
int32_t CommUDPRecv(CommUDPRef *pRef, void *pTarget, int32_t iLength, uint32_t *pWhen)
{
// use peek to remove the data
int32_t iLen = CommUDPPeek(pRef, pTarget, iLength, pWhen);
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));
}