/*H*************************************************************************************************/ /*! \File commsrp.c \Description This is CommSRP (Selectively Reliable Protocol), a datagram packet-based transport class. \Copyright Copyright (c) Tiburon Entertainment / Electronic Arts 1999-2003. ALL RIGHTS RESERVED. \Version 0.5 01/03/03 (JLB) Initial Version, based on CommTCP \Version 0.7 01/07/03 (JLB) Working unreliable transport, based on CommUDP \Version 0.8 01/08/03 (JLB) Working reliable transport. \Version 0.9 02/09/03 (JLB) Added support for sending zero-byte packets, and fixed PS2 alignment issue. */ /*************************************************************************************************H*/ /*** Include files *********************************************************************/ #define ESC_CAUSES_LOSS (0) #if ESC_CAUSES_LOSS #include #endif #include #include #include "DirtySDK/dirtysock.h" #include "DirtySDK/dirtysock/dirtymem.h" #include "DirtySDK/comm/commall.h" #include "DirtySDK/comm/commsrp.h" /*** Defines ***************************************************************************/ //! enable debug spam #define COMMSRP_VERBOSE (COMM_PRINT && FALSE) //! control packet types enum { CTRL_PACKET_FIRST = 16, //!< 16: beginning of packet code range CTRL_PACKET_INIT = CTRL_PACKET_FIRST, //!< 16: init packet CTRL_PACKET_POKE, //!< 17: firewall poke packet CTRL_PACKET_CONN, //!< 18: connection confirmation packet CTRL_PACKET_KEEP, //!< 19: keep-alive packet CTRL_PACKET_DISC, //!< 20: connection terminated packet CTRL_PACKET_LAST = 63 //!< 63: end of packet code range }; //! size of sequence set #define SEQN_SIZE (64) //! sequence set mask #define SEQN_MASK (SEQN_SIZE-1) //! base of unreliable sequence set #define UNRELSEQN_BASE (64) //! base of reliable sequence set #define RELSEQN_BASE (128) //! base of reliable packet reception acknowledgement sequence set #define RELSEQNACK_BASE (192) //! rate at which to resend unacknoweldged packets #define RELIABLE_RESEND_RATE (250) //! percentage of packets in receive buffer reserved for reliable packets (divisor) #define RELIABLE_PCT_RESERVED (8) /*** Macros ****************************************************************************/ /*** Type Definitions ******************************************************************/ //! raw protocol packet format /*! Notes: The RawSRPPacket code field looks like this: value(s) meaning ~~~~~~~~~~~~~~~~~~~~~~ 0 reserved 1-15 bundled packet count 16 ctrl packet: init connection (CTRL_PACKET_INIT) 17 ctrl packet: poke firewall (CTRL_PACKET_POKE) 18 ctrl packet: connection established (CTRL_PACKET_CONN) 19 ctrl packet: keep-alive (CTRL_PACKET_KEEP) 20 ctrl packet: disconnect (CTRL_PACKET_DISC) 21-63 reserved 64-127 unreliable packet: sequence number is code-64 128-191 reliable packet: sequence number is code-128 192-255 reliable packet acknowledgement: sequence number is code-192 ~~~~~~~~~~~~~~~~~~~~~~ If the code field is 1-15, then the value represents the number of packets bundled together into the same UDP frame minus one (a value of one is unsupported, as it would simply make the single packet bulkier). The bundle format looks as follows: offset value meaning ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 0 code bundle count-1 (1-15) 1 len0-1 length of first bundled packet 2 code0 code of first packet 3 data0 data for first packet 4+len0 len1-1 length of second bundled packet 5+len0 code1 code of second packet 6+len0 data1 data for second packet [...] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ typedef struct { //! packet header which is not sent/received //! (this data is used internally) struct { uint32_t when; //!< tick when a packet was received uint32_t len; //!< size of packet } head; //! packet body which is sent/received struct { unsigned char code; //!< packet code (see packet codes above) unsigned char data[1]; //!< packet user data (variable length) } body; } RawSRPPacketT; //! private module storage struct CommSRPRef { //! common header CommRef common; //! linked list of all instances CommSRPRef *link; //! comm socket SocketT *socket; //! peer address struct sockaddr peeraddr; //! port state enum { ST_IDLE, //!< no connection ST_CONN, //!< connecting ST_LIST, //!< listening ST_OPEN, //!< connection established ST_CLOSE //!< connection closed } state; //! identifier to keep from getting spoofed uint32_t connident; //! width of receive records (same as width of send) int32_t rcvwid; //! number of packets reserved for reliable transport int32_t rcvrelresv; //! length of receive buffer (multiple of rcvwid) int32_t rcvlen; //! fifo input offset int32_t rcvinp; //! fifo output offset int32_t rcvout; //! pointer to buffer storage char *rcvbuf; //! width of send record (same as width of receive) int32_t sndwid; //! length of send buffer (multiple of sndwid) int32_t sndlen; //! fifo input offset int32_t sndinp; //! fifo output offset int32_t sndout; //! pointer to buffer storage char *sndbuf; //! tick at which last packet was sent uint32_t sendtick; //! tick at which last packet was received uint32_t recvtick; //! unreliable sequence number uint32_t unrelseqn; //! unreliable sequence number uint32_t unrelrecvseqn; //! reliable sequence number uint32_t relseqn; //! reliable received sequence number uint32_t relrecvseqn; //! allow for dns lookup uint32_t dnsid; char dnsquery[256]; char *dnsbuf; int32_t dnslen; char dnsdiv; //! semaphore to synchronize thread access NetCritT crit; //! callback synchronization volatile int32_t callback; //! indcate pending event int32_t gotevent; //! callback routine pointer void (*callproc)(CommRef *ref, int32_t event); }; /*** Function Prototypes ***************************************************************/ /*** Variables *************************************************************************/ // Private variables // Public variables /*** Private Functions *****************************************************************/ /*F*************************************************************************************************/ /*! \Function _CommSRPSetAddrInfo \Description Sets peer/host addr/port info in common ref. \Input *ref - reference pointer \Input sin - address pointer \Version 04/16/04 (JLB) */ /*************************************************************************************************F*/ static void _CommSRPSetAddrInfo(CommSRPRef *ref, struct sockaddr *sin) { struct sockaddr SockAddr; // save peer addr/port info in common ref ref->common.peerip = SockaddrInGetAddr(sin); ref->common.peerport = SockaddrInGetPort(sin); // save host addr/port info in common ref SocketInfo(ref->socket, 'bind', 0, &SockAddr, sizeof(SockAddr)); ref->common.hostip = SocketGetLocalAddr(); ref->common.hostport = SockaddrInGetPort(&SockAddr); // debug output NetPrintf(("commsrp: peer=0x%08x:%d, host=0x%08x:%d\n", ref->common.peerip, ref->common.peerport, ref->common.hostip, ref->common.hostport)); } /*F*************************************************************************************************/ /*! \Function _CommSRPSetSocket \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 _CommSRPSetSocket(CommSRPRef *pRef, SocketT *pSocket) { pRef->socket = pSocket; pRef->common.sockptr = pSocket; } /*F*************************************************************************************************/ /*! \Function _CommSRPResetTransfer \Description Reset the transfer state \Input *ref - reference pointer \Version 01/03/03 (JLB) */ /*************************************************************************************************F*/ static void _CommSRPResetTransfer(CommSRPRef *ref) { // reset the send queue ref->sndinp = 0; ref->sndout = 0; // reset the receive queue ref->rcvinp = 0; ref->rcvout = 0; // make sendtick really old (in protocol terms) ref->sendtick = NetTick()-5000; // start reliable received sequence at an invalid sequence number ref->relrecvseqn = (uint32_t)-1; } /*F*************************************************************************************************/ /*! \Function _CommSRPSendImmediate \Description Send an packet to the peer. \Input *ref - reference pointer \Input *packet - packet pointer \Output int32_t - negative=error, else amount of data sent \Version 01/07/03 (JLB) */ /*************************************************************************************************F*/ static int32_t _CommSRPSendImmediate(CommSRPRef *ref, RawSRPPacketT *packet) { int32_t len, err; #if ESC_CAUSES_LOSS // lose packets when escape is pressed if (GetAsyncKeyState(VK_ESCAPE) < 0) { ref->sendtick = NetTick(); return(0); } #endif // figure out amount to send len = sizeof(packet->body)-sizeof(packet->body.data); // packet framing len += packet->head.len; // variable data #if COMMSRP_VERBOSE { char addrbuf[32]; SockaddrInGetAddrText(&ref->peeraddr,addrbuf,sizeof(addrbuf)), NetPrintf(("_CommSRPSendImmediate: Sending %d bytes to %s:%d\n",len, addrbuf, SockaddrInGetPort(&ref->peeraddr))); } #endif // send some data err = SocketSendto(ref->socket,(char *)&packet->body,len,0, &ref->peeraddr,sizeof(ref->peeraddr)); // check for send failure if (err != len) { NetPrintf(("_CommSRPSendImmediate: SocketSendto returned %d\n", err)); return(-1); } // update last send time ref->sendtick = NetTick(); ref->common.datasent += len; ref->common.packsent += 1; return(len); } /*F*************************************************************************************************/ /*! \Function _CommSRPSendCtrl \Description Send a control packet. \Input *ref - reference pointer \Input code - control code to send \Notes Control packets are sent unreliably. \Version 01/08/03 (JLB) */ /*************************************************************************************************F*/ static void _CommSRPSendCtrl(CommSRPRef *ref, unsigned char code) { RawSRPPacketT packet; #if COMMSRP_VERBOSE NetPrintf(("_CommSRPSendCtrl: Sending control packet id %d\n", code)); #endif packet.head.len = 0; packet.body.code = code; _CommSRPSendImmediate(ref, &packet); } /*F*************************************************************************************************/ /*! \Function _CommSRPProcessInitRequest \Description Send an INIT control packet if we know our peer. \Input *ref - reference pointer \Version 01/08/03 (JLB) */ /*************************************************************************************************F*/ static void _CommSRPProcessInitRequest(CommSRPRef *ref) { // see if we know our peer if ((SockaddrInGetAddr(&ref->peeraddr) == 0) || (SockaddrInGetPort(&ref->peeraddr) == 0)) { return; } // send connect message once a second if ((NetTick() - ref->sendtick) >= 1000) { if (ref->state == ST_CONN) { // send connection initiation packet _CommSRPSendCtrl(ref, CTRL_PACKET_INIT); } else if (ref->state == ST_LIST) { // send poke packet _CommSRPSendCtrl(ref, CTRL_PACKET_POKE); } } } /*F*************************************************************************************************/ /*! \Function _CommSRPProcessACK \Description Process a reliable packet acknowledgement \Input *ref - reference pointer \Input *packet - ack packet \Version 01/08/03 (JLB) */ /*************************************************************************************************F*/ static void _CommSRPProcessACK(CommSRPRef *ref, RawSRPPacketT *packet) { uint32_t ackseqnid, refseqnid; RawSRPPacketT *refpkt; // get a pointer to packet this should be an ack for refpkt = (RawSRPPacketT *)&ref->sndbuf[ref->sndout]; // decode sequence ids ackseqnid = packet->body.code - RELSEQNACK_BASE; refseqnid = refpkt->body.code - RELSEQN_BASE; // compare sequence ids if (ackseqnid == refseqnid) { // packet was successfully transmitted, so dequeue it ref->sndout = (ref->sndout+ref->sndwid)%ref->sndlen; #if COMMSRP_VERBOSE NetPrintf(("_CommSRPProcessACK: Success: confirmed delivery of packet %d\n", refseqnid)); #endif } else { #if COMMSRP_VERBOSE NetPrintf(("_CommSRPProcessACK: Got old ack %d, wanted ack %d\n", ackseqnid,refseqnid)); #endif } } /*F*************************************************************************************************/ /*! \Function _CommSRPProcessCtrl \Description Process an incoming control message. \Input *ref - reference pointer \Input *packet - control packet to process \Input *pFrom - sender's socket address \Version 01/07/03 (JLB) */ /*************************************************************************************************F*/ static void _CommSRPProcessCtrl(CommSRPRef *ref, RawSRPPacketT *packet, struct sockaddr *pFrom) { // update valid receive time // must put into past to avoid race condition ref->recvtick = NetTick()-1000; switch(packet->body.code) { // response to connection request packet case CTRL_PACKET_INIT: { #if COMMSRP_VERBOSE NetPrintf(("_CommSRPProcessSetup: Received INIT\n")); #endif if (ref->state == ST_LIST) { // set the peer ds_memcpy_s(&ref->peeraddr, sizeof(ref->peeraddr), pFrom, sizeof(*pFrom)); // set peer/host addr/port info _CommSRPSetAddrInfo(ref, pFrom); // update state ref->state = ST_OPEN; } // always send a response _CommSRPSendCtrl(ref, CTRL_PACKET_CONN); } break; // response to poke packet case CTRL_PACKET_POKE: { #if COMMSRP_VERBOSE NetPrintf(("_CommSRPProcessSetup: Received POKE\n")); #endif if (ref->state == ST_CONN) { // set the peer ds_memcpy_s(&ref->peeraddr, sizeof(ref->peeraddr), pFrom, sizeof(*pFrom)); } } break; // response to a connect confirmation case CTRL_PACKET_CONN: { #if COMMSRP_VERBOSE NetPrintf(("_CommSRPProcessSetup: Received CONN\n")); #endif // change to open if not already there if (ref->state == ST_CONN) { // set peer/host addr/port info _CommSRPSetAddrInfo(ref, pFrom); ref->state = ST_OPEN; } } break; // response to disconnect message case CTRL_PACKET_DISC: { #if COMMSRP_VERBOSE NetPrintf(("_CommSRPProcessSetup: Received DISC\n")); #endif // close the connection if (ref->state == ST_OPEN) { ref->state = ST_CLOSE; } } break; // response to keepalive message case CTRL_PACKET_KEEP: { #if COMMSRP_VERBOSE NetPrintf(("_CommSRPProcessSetup: Received KEEP\n")); #endif } break; // this case should not happen default: { NetPrintf(("_CommSRPProcessSetup: Unrecognized control packet type %d\n",packet->body.code)); } break; } } /*F*************************************************************************************************/ /*! \Function _CommSRPProcessData \Description Process incoming data packet. \Input *ref - reference pointer \Input *packet - incoming packet \Output int32_t - <0 = error, 0 = packet discarded (duplicate resend), 1 = packet added to buffer \Version 01/07/03 (JLB) */ /*************************************************************************************************F*/ static int32_t _CommSRPProcessData(CommSRPRef *ref, RawSRPPacketT *packet) { RawSRPPacketT *buffer; if ((packet->body.code >= UNRELSEQN_BASE) && (packet->body.code < RELSEQN_BASE)) { int32_t queuepos, pktsfree; // calculate the number of free packets in rcvbuf queuepos = ((ref->rcvinp+ref->rcvlen)-ref->rcvout)%ref->rcvlen; pktsfree = (ref->rcvlen - queuepos)/ref->rcvwid; // see if there is room in buffer for packet (leave extra space for reliable packets) if (pktsfree <= ref->rcvrelresv) { // no room in buffer -- just drop packet #if COMMSRP_VERBOSE // (we expect lots of these, so don't spam) NetPrintf(("_CommSRPProcessData: Unreliable packet %d discarded due to input buffer overrun.\n",ref->relrecvseqn)); #endif return(-1); } #if COMMSRP_VERBOSE NetPrintf(("_CommSRPProcessData: Received packet %d\n",packet->body.code - UNRELSEQN_BASE)); #endif // update unreliably received sequence number ref->unrelrecvseqn = packet->body.code - UNRELSEQN_BASE; } else { uint32_t uPacketId; // see if room in buffer for packet if ((ref->rcvinp+ref->rcvwid)%ref->rcvlen == ref->rcvout) { // no room in buffer -- just drop packet NetPrintf(("_CommSRPProcessData: Reliable packet discarded due to input buffer overrun\n")); return(-1); } // get packet ID uPacketId = (uint32_t)(packet->body.code - RELSEQN_BASE); // is this the packet we're expecting? if (uPacketId == ((ref->relrecvseqn+1) % SEQN_SIZE)) { // update reliably received sequence number ref->relrecvseqn = uPacketId; #if COMMSRP_VERBOSE NetPrintf(("_CommSRPProcessData: Received packet %d\n", ref->relrecvseqn)); #endif } else if (uPacketId == ref->relrecvseqn) { // this is the previously received packet - return TRUE to ack it in case our last ack got lost return(1); } else { #if COMMSRP_VERBOSE NetPrintf(("_CommSRPProcessData: Discarding duplicate packet %d\n",ref->relrecvseqn)); #endif return(0); } } // add the packet to the buffer buffer = (RawSRPPacketT *) &ref->rcvbuf[ref->rcvinp]; ds_memcpy(buffer, packet, ref->rcvwid); // limit receive access for callbacks ref->callback += 1; // add item to receive buffer ref->rcvinp = (ref->rcvinp+ref->rcvwid) % ref->rcvlen; // indicate we got an event ref->gotevent |= 1; // let the callback process it if (ref->common.RecvCallback != NULL) { ref->common.RecvCallback((CommRef *)ref, buffer->body.data, buffer->head.len, buffer->head.when); } // release access to receive ref->callback -= 1; return(1); } /*F*************************************************************************************************/ /*! \Function _CommSRPProcessAlive \Description Send out a keep-alive packet (CTRL_PACKET_KEEP) periodically \Input *ref - reference pointer \Version 01/07/03 (JLB) */ /*************************************************************************************************F*/ static void _CommSRPProcessAlive(CommSRPRef *ref) { if ((ref->state == ST_OPEN) && (ref->sndinp == ref->sndout)) { // see if time has elapsed if (NetTick() > ref->sendtick+5000) { // queue up a keepalive packet _CommSRPSendCtrl(ref, CTRL_PACKET_KEEP); } } } /*F*************************************************************************************************/ /*! \Function _CommSRPProcessRecvQueue \Description Attempt to read data from peer and add it to the receive queue. \Input *ref - reference pointer \Version 01/08/03 (JLB) */ /*************************************************************************************************F*/ static void _CommSRPProcessRecvQueue(CommSRPRef *ref) { int32_t len, sinlen, iResult = 0; char *buffer; struct sockaddr sin; RawSRPPacketT *packet; while((ref->state >= ST_CONN) && (ref->state <= ST_OPEN) && (iResult >= 0)) { // get pointer to recv queue packet = (RawSRPPacketT *) &ref->rcvbuf[ref->rcvinp]; buffer = (char *) &packet->body; // attempt to get a packet sinlen = sizeof(sin); len = SocketRecvfrom(ref->socket, buffer, ref->rcvwid, 0, &sin, &sinlen); // if we got data add it to recv queue if (len > 0) { #if COMMSRP_VERBOSE NetPrintf(("_CommSRPProcessRecvQueue: Received %d bytes of data\n",len)); #endif packet->head.len = len; packet->head.when = SockaddrInGetMisc(&sin); // process packet if ((packet->body.code >= CTRL_PACKET_FIRST) && (packet->body.code <= CTRL_PACKET_LAST)) { // handle control packets _CommSRPProcessCtrl(ref, packet, &sin); } else if (packet->body.code >= RELSEQNACK_BASE) { // handle ack of reliable packet _CommSRPProcessACK(ref, packet); } else { // handle data packets iResult = _CommSRPProcessData(ref, packet); if (iResult == 1) { // check to see if this is a reliable packet and requires an acknowledgement if ((packet->body.code >= RELSEQN_BASE) && (packet->body.code < RELSEQNACK_BASE)) { // send an ack (seqid +64) #if COMMSRP_VERBOSE NetPrintf(("_CommSRPProcessRecvQueue: Sending ack for packet %d\n",packet->body.code)); #endif _CommSRPSendCtrl(ref,packet->body.code + SEQN_SIZE); } } } } else if (len < 0) { // error in recv - close connection NetPrintf(("_CommSRPProcessRecvQueue: Error %d - closing connection\n",len)); ref->state = ST_CLOSE; break; } else { // no data to receive break; } } } /*F*************************************************************************************************/ /*! \Function _CommSRPProcessSendQueue \Description Send data to peer from send queue. \Input *ref - reference pointer \Version 01/08/03 (JLB) */ /*************************************************************************************************F*/ static void _CommSRPProcessSendQueue(CommSRPRef *ref) { RawSRPPacketT *packet; if ((ref->state == ST_OPEN) && (ref->sndinp != ref->sndout)) { // ref next packet to send packet = (RawSRPPacketT *) &ref->sndbuf[ref->sndout]; // send packet if ((packet->head.when == 0) || ((NetTick() - packet->head.when) >= RELIABLE_RESEND_RATE)) { #if COMMSRP_VERBOSE if (packet->head.when != 0) { NetPrintf(("_CommSRPProcessSendQueue: Resending sequence id %d\n", packet->body.code - RELSEQN_BASE)); } #endif #if COMMSRP_VERBOSE if (packet->head.when == 0) { NetPrintf(("_CommSRPProcessSendQueue: Sending sequence id %d\n", packet->body.code - RELSEQN_BASE)); } #endif // send the packet if (_CommSRPSendImmediate(ref, packet) < 0) { return; } // update sent tick packet->head.when = NetTick(); } } } /*F*************************************************************************************************/ /*! \Function _CommSRPClose \Description Close the connection \Input *ref - reference pointer \Output int32_t - zero \Version 01/03/03 (JLB) */ /*************************************************************************************************F*/ static int32_t _CommSRPClose(CommSRPRef *ref) { // see if we are even connected if (ref->state != ST_OPEN) { return(0); } // send a disconnect message _CommSRPSendCtrl(ref, CTRL_PACKET_DISC); // set to disconnect state ref->connident = 0; ref->state = ST_CLOSE; return(0); } /*F*************************************************************************************************/ /*! \Function _CommSRPEvent0 \Description Private socket callback function for CommSRP event processing. \Input *ref - reference pointer \Notes This function should only be called by _CommSRPEvent() \Version 01/08/03 (JLB) */ /*************************************************************************************************F*/ static void _CommSRPEvent0(CommSRPRef *ref) { // send init request if we're connecting if ((ref->state == ST_CONN) || (ref->state == ST_LIST)) { _CommSRPProcessInitRequest(ref); } // receive data into recv queue _CommSRPProcessRecvQueue(ref); // periodically send a keep-alive _CommSRPProcessAlive(ref); // send all queued data _CommSRPProcessSendQueue(ref); // do callback if needed if ((ref->callback == 0) && (ref->gotevent != 0)) { // limit callback access ref->callback += 1; // notify user if (ref->callproc != NULL) { ref->callproc((CommRef *)ref, ref->gotevent); } // limit callback access ref->callback -= 1; // done with event ref->gotevent = 0; } } /*F*************************************************************************************************/ /*! \Function _CommSRPEvent \Description Socket callback function for CommSRP event processing, protected with critical section. \Input *sock - unused \Input flags - unused \Input *_ref - reference pointer \Output int32_t - zero \Version 01/08/03 (JLB) */ /*************************************************************************************************F*/ static int32_t _CommSRPEvent(SocketT *sock, int32_t flags, void *_ref) { CommSRPRef *ref = _ref; // see if we have exclusive access if (NetCritTry(&ref->crit)) { // update _CommSRPEvent0(ref); // free access NetCritLeave(&ref->crit); } return(0); } /*F*************************************************************************************************/ /*! \Function _CommSRPSendQueued \Description Add packet to send queue. \Input *ref - reference pointer \Input *packet - packet to add to send queue \Output int32_t - buffer depth \Notes Only reliable packets should be added to the send queue; use _CommSRPSendImmediate() for unreliable packet data. \Version 01/08/03 (JLB) */ /*************************************************************************************************F*/ static int32_t _CommSRPSendQueued(CommSRPRef *ref, RawSRPPacketT *packet) { int32_t pos; // mark timestamp=0 for immediate send packet->head.when = 0; // add the packet to the queue ref->sndinp = (ref->sndinp+ref->sndwid) % ref->sndlen; // try and send immediately _CommSRPEvent(ref->socket, 0, ref); // return buffer depth pos = (((ref->sndinp+ref->sndlen)-ref->sndout)%ref->sndlen)/ref->sndwid; return(pos); } /*** Public Functions ******************************************************************/ /*F*************************************************************************************************/ /*! \Function CommSRPConstruct \Description Construct the class \Input maxwid - max record width \Input maxinp - input packet buffer size \Input maxout - output packet buffer size \Output CommSRPRef - reference 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 01/03/03 (JLB) */ /*************************************************************************************************F*/ CommSRPRef *CommSRPConstruct(int32_t maxwid, int32_t maxinp, int32_t maxout) { CommSRPRef *ref; int32_t iMemGroup; void *pMemGroupUserData; // Query current mem group data DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); // allocate class storage ref = DirtyMemAlloc(sizeof(*ref), COMMSRP_MEMID, iMemGroup, pMemGroupUserData); if (ref == NULL) return(NULL); ds_memclr(ref, sizeof(*ref)); ref->common.memgroup = iMemGroup; ref->common.memgrpusrdata = pMemGroupUserData; // initialize the callback routines ref->common.Construct = (CommAllConstructT *)CommSRPConstruct; ref->common.Destroy = (CommAllDestroyT *)CommSRPDestroy; ref->common.Resolve = (CommAllResolveT *)CommSRPResolve; ref->common.Unresolve = (CommAllUnresolveT *)CommSRPUnresolve; ref->common.Listen = (CommAllListenT *)CommSRPListen; ref->common.Unlisten = (CommAllUnlistenT *)CommSRPUnlisten; ref->common.Connect = (CommAllConnectT *)CommSRPConnect; ref->common.Unconnect = (CommAllUnconnectT *)CommSRPUnconnect; ref->common.Callback = (CommAllCallbackT *)CommSRPCallback; ref->common.Status = (CommAllStatusT *)CommSRPStatus; ref->common.Tick = (CommAllTickT *)CommSRPTick; ref->common.Send = (CommAllSendT *)CommSRPSend; ref->common.Peek = (CommAllPeekT *)CommSRPPeek; ref->common.Recv = (CommAllRecvT *)CommSRPRecv; // remember max packet width ref->common.maxwid = maxwid; ref->common.maxinp = maxinp; ref->common.maxout = maxout; // reset access to shared resources NetCritInit(&ref->crit, "commsrp"); // allocate the buffers ref->rcvwid = sizeof(RawSRPPacketT)-sizeof(((RawSRPPacketT *)0)->body.data)+maxwid; ref->rcvwid = (ref->rcvwid+3) & 0x7ffc; ref->rcvlen = ref->rcvwid * maxinp; ref->rcvbuf = DirtyMemAlloc(ref->rcvlen, COMMSRP_MEMID, ref->common.memgroup, ref->common.memgrpusrdata); ref->sndwid = sizeof(RawSRPPacketT)-sizeof(((RawSRPPacketT *)0)->body.data)+maxwid; ref->sndwid = (ref->sndwid+3) & 0x7ffc; ref->sndlen = ref->sndwid * maxout; ref->sndbuf = DirtyMemAlloc(ref->sndlen, COMMSRP_MEMID, ref->common.memgroup, ref->common.memgrpusrdata); // calculate number of packets reserved for reliable transport ref->rcvrelresv = maxinp/RELIABLE_PCT_RESERVED; // reset the socket _CommSRPSetSocket(ref, NULL); // reset the state ref->state = ST_IDLE; ref->connident = 0; // return the reference return(ref); } /*F*************************************************************************************************/ /*! \Function CommSRPDestroy \Description Destruct the class \Input *ref - reference pointer \Version 01/03/03 (JLB) */ /*************************************************************************************************F*/ void CommSRPDestroy(CommSRPRef *ref) { // if port is open, close it if (ref->state == ST_OPEN) { _CommSRPClose(ref); } // kill the socket if (ref->socket != NULL) { SocketClose(ref->socket); _CommSRPSetSocket(ref, NULL); } // get rid of sockets critical section NetCritKill(&ref->crit); // release resources DirtyMemFree(ref->rcvbuf, COMMSRP_MEMID, ref->common.memgroup, ref->common.memgrpusrdata); DirtyMemFree(ref->sndbuf, COMMSRP_MEMID, ref->common.memgroup, ref->common.memgrpusrdata); DirtyMemFree(ref, COMMSRP_MEMID, ref->common.memgroup, ref->common.memgrpusrdata); } /*F*************************************************************************************************/ /*! \Function CommSRPCallback \Description Set upper layer callback \Input *ref - reference pointer \Input *callback - socket generating callback \Version 01/03/03 (JLB) */ /*************************************************************************************************F*/ void CommSRPCallback(CommSRPRef *ref, void (*callback)(CommRef *ref, int32_t event)) { ref->callproc = callback; ref->gotevent |= 2; } /*F*************************************************************************************************/ /*! \Function CommSRPResolve \Description Resolve an address (unimplemented) \Input *ref - endpoint \Input *addr - resolve address \Input *buf - target buffer \Input len - target length (min 64 bytes) \Input div - 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 01/03/03 (JLB) */ /*************************************************************************************************F*/ int32_t CommSRPResolve(CommSRPRef *ref, const char *addr, char *buf, int32_t len, char div) { NetPrintf(("commsrp: resolve functionality not supported\n")); return(-1); } /*F*************************************************************************************************/ /*! \Function CommSRPUnresolve \Description Stop the resolver \Input *ref - endpoint ref \Version 01/03/03 (JLB) */ /*************************************************************************************************F*/ void CommSRPUnresolve(CommSRPRef *ref) { return; } /*F*************************************************************************************************/ /*! \Function CommSRPUnlisten \Description Stop listening. \Input *ref - reference pointer \Output int32_t - negative=error, zero=ok \Version 01/03/03 (JLB) */ /*************************************************************************************************F*/ int32_t CommSRPUnlisten(CommSRPRef *ref) { // get rid of socket if presernt if (ref->socket != NULL) { SocketClose(ref->socket); _CommSRPSetSocket(ref, NULL); } // return to idle mode ref->state = ST_IDLE; return(0); } /*F*************************************************************************************************/ /*! \Function CommSRPUnconnect \Description Terminate a connection \Input *ref - reference pointer \Output int32_t - negative=error, zero=ok \Version 01/03/03 (JLB) */ /*************************************************************************************************F*/ int32_t CommSRPUnconnect(CommSRPRef *ref) { // get rid of socket if presernt if (ref->socket != NULL) { SocketClose(ref->socket); _CommSRPSetSocket(ref, NULL); } // return to idle mode ref->state = ST_IDLE; return(0); } /*F*************************************************************************************************/ /*! \Function CommSRPStatus \Description Return current stream status \Input *ref - reference pointer \Output int32_t - COMM_CONNECTING, COMM_OFFLINE, COMM_ONLINE or COMM_FAILURE \Version 01/03/03 (JLB) */ /*************************************************************************************************F*/ int32_t CommSRPStatus(CommSRPRef *ref) { // return state if ((ref->state == ST_CONN) || (ref->state == ST_LIST)) return(COMM_CONNECTING); if ((ref->state == ST_IDLE) || (ref->state == ST_CLOSE)) return(COMM_OFFLINE); if (ref->state == ST_OPEN) return(COMM_ONLINE); return(COMM_FAILURE); } /*F*************************************************************************************************/ /*! \Function CommSRPTick \Description Return current tick \Input *ref - reference pointer \Output uint32_t - elapsed milliseconds \Version 01/03/03 (JLB) */ /*************************************************************************************************F*/ uint32_t CommSRPTick(CommSRPRef *ref) { return(NetTick()); } /*F*************************************************************************************************/ /*! \Function CommSRPSend \Description Send a packet \Input *ref - reference pointer \Input *buffer - pointer to data \Input length - length of data \Input flags - COMM_FLAGS_* \Output int32_t - negative=error, zero=buffer full (temp fail), positive=queue position (ok) \Version 01/03/03 (JLB) */ /*************************************************************************************************F*/ int32_t CommSRPSend(CommSRPRef *ref, const void *buffer, int32_t length, uint32_t flags) { RawSRPPacketT *packet; int32_t pos; // make sure port is open if (ref->state != ST_OPEN) { return(COMM_BADSTATE); } // see if input queue full if ((ref->sndinp+ref->sndwid)%ref->sndlen == ref->sndout) { NetPrintf(("commsrp: input queue full\n")); return(0); } // return error for oversized packets if (length > (signed)(ref->sndwid-(sizeof(RawSRPPacketT)-sizeof((RawSRPPacketT *)0)->body.data))) { NetPrintf(("commsrp: oversized packet send (%d bytes)\n", length)); return(COMM_MINBUFFER); } // zero sized packet cannot be sent (they are used for acks) // instead, treat them as successful which means the queue // position is returned if (length == 0) { pos = (((ref->sndinp+ref->sndlen)-ref->sndout)%ref->sndlen)/ref->sndwid; return(pos+1); } // add packet to send queue packet = (RawSRPPacketT *) &(ref->sndbuf[ref->sndinp]); // set packet length packet->head.len = length; // copy user data into queue ds_memcpy(packet->body.data, buffer, length); // send reliable or unreliable? if (flags & COMM_FLAGS_UNRELIABLE) { // unreliable sequence tracking packet->body.code = (unsigned char)ref->unrelseqn++ + UNRELSEQN_BASE; ref->unrelseqn &= SEQN_MASK; #if COMMSRP_VERBOSE NetPrintf(("commsrp: sending unreliable sequence number %d (len=%d)\n",packet->body.code-UNRELSEQN_BASE,length)); #endif // unreliable packets are sent immediately if ((pos = _CommSRPSendImmediate(ref,packet)) > 0) { pos = 1; } } else { // reliable sequence tracking packet->body.code = (unsigned char)ref->relseqn++ + RELSEQN_BASE; ref->relseqn &= SEQN_MASK; #if COMMSRP_VERBOSE NetPrintf(("CommSRPSend: Sending reliable sequence number %d (len=%d)\n",packet->body.code-RELSEQN_BASE,length)); #endif // reliable packets are added to the send queue pos = _CommSRPSendQueued(ref,packet); } // return buffer depth return((pos > 0) ? pos : 1); } /*F*************************************************************************************************/ /*! \Function CommSRPPeek \Description Peek at waiting packet, and copy to target buffer if present. \Input *ref - reference pointer \Input *target - target buffer \Input length - buffer length \Input *when - tick received at \Output int32_t - negative=nothing pending, else packet length \Version 01/03/03 (JLB) */ /*************************************************************************************************F*/ int32_t CommSRPPeek(CommSRPRef *ref, void *target, int32_t length, uint32_t *when) { RawSRPPacketT *packet; int32_t packetlen; // see if a packet is available if (ref->rcvout == ref->rcvinp) { return(COMM_NODATA); } // point to the packet packet = (RawSRPPacketT *) &(ref->rcvbuf[ref->rcvout]); #if COMMSRP_VERBOSE NetPrintf(("commsrp: received packet, sequence number %d (len=%d)\n",packet->body.code,packet->head.len)); #endif // packet length minus code byte packetlen = packet->head.len - (sizeof(packet->body)-sizeof(packet->body.data)); // copy over the data portion ds_memcpy(target, packet->body.data, (packetlen < length ? packetlen : length)); // get the timestamp if (when != NULL) { *when = packet->head.when; } // return packet data length return(packetlen); } /*F*************************************************************************************************/ /*! \Function CommSRPRecv \Description Receive a packet from the buffer \Input *ref - reference pointer \Input *target - target buffer \Input length - buffer length \Input *when - tick received at \Output int32_t - negative=error, else packet length \Version 01/03/03 (JLB) */ /*************************************************************************************************F*/ int32_t CommSRPRecv(CommSRPRef *ref, void *target, int32_t length, uint32_t *when) { // use peek to remove the data int32_t len = CommSRPPeek(ref, target, length, when); if (len >= 0) { ref->rcvout = (ref->rcvout+ref->rcvwid)%ref->rcvlen; } // all done return(len); } /*F*************************************************************************************************/ /*! \Function CommSRPListen \Description Listen for a connection \Input *ref - reference pointer \Input *addr - port to listen on (only :port portion used) \Output int32_t - negative=error, zero=ok \Version 01/03/03 (JLB) */ /*************************************************************************************************F*/ int32_t CommSRPListen(CommSRPRef *ref, const char *addr) { int32_t err, iListenPort, iConnPort; uint32_t uPokeAddr; struct sockaddr bindaddr; SocketT *sock; // make sure in valid state if ((ref->state != ST_IDLE) || (ref->socket != NULL)) { return(COMM_BADSTATE); } // setup bind points SockaddrInit(&bindaddr, AF_INET); // parse at least port if ((SockaddrInParse2(&uPokeAddr, &iListenPort, &iConnPort, addr) & 0x2) != 0x2) { return(COMM_BADADDRESS); } SockaddrInSetPort(&bindaddr, iListenPort); // reset to unresolved _CommSRPResetTransfer(ref); // create socket in case its needed sock = SocketOpen(AF_INET, SOCK_DGRAM, 0); _CommSRPSetSocket(ref, sock); if (ref->socket == NULL) { return(COMM_NORESOURCE); } // bind locally #if COMMSRP_VERBOSE NetPrintf(("commsrp: binding to port %d\n", SockaddrInGetPort(&bindaddr))); #endif if ((err = SocketBind(ref->socket, &bindaddr, sizeof(bindaddr))) < 0) { NetPrintf(("commsrp: error %d binding socket\n", err)); SocketClose(ref->socket); _CommSRPSetSocket(ref, NULL); return(COMM_UNEXPECTED); } // setup for callbacks SocketCallback(ref->socket, CALLB_RECV, 100, ref, &_CommSRPEvent); // see if we should setup peer address if (uPokeAddr != 0) { if (iConnPort == 0) { iConnPort = iListenPort+1; } SockaddrInit(&ref->peeraddr, AF_INET); SockaddrInSetAddr(&ref->peeraddr, uPokeAddr); SockaddrInSetPort(&ref->peeraddr, iConnPort); } // put into init mode ref->state = ST_LIST; return(0); } /*F*************************************************************************************************/ /*! \Function CommSRPConnect \Description Initiate a connection to a peer \Input *ref - reference pointer \Input *pAddr - address in ip-address:port form \Output int32_t - negative=error, zero=ok \Notes Does not perform dns translation \Version 01/03/03 (JLB) */ /*************************************************************************************************F*/ int32_t CommSRPConnect(CommSRPRef *ref, const char *pAddr) { struct sockaddr bindaddr; int32_t iErr, iConnPort, iListenPort; uint32_t uAddr; SocketT *sock; // make sure in valid state if ((ref->state != ST_IDLE) || (ref->socket != NULL)) { return(COMM_BADSTATE); } // parse address and port from addr string if ((SockaddrInParse2(&uAddr, &iListenPort, &iConnPort, pAddr) & 0x3) != 0x3) { return(COMM_BADADDRESS); } // if we don't have an alternate connect port: connect=listen, listen=listen+1 if (iConnPort == 0) { iConnPort = iListenPort++; } // reset to unresolved _CommSRPResetTransfer(ref); // create the actual socket sock = SocketOpen(AF_INET, SOCK_DGRAM, 0); _CommSRPSetSocket(ref, sock); if (ref->socket == NULL) { return(COMM_NORESOURCE); } // set listen port SockaddrInit(&bindaddr, AF_INET); SockaddrInSetPort(&bindaddr, iListenPort); // bind to port #if COMMSRP_VERBOSE NetPrintf(("commsrp: binding to port %d\n", iListenPort)); #endif if ((iErr = SocketBind(ref->socket, &bindaddr, sizeof(bindaddr))) < 0) { NetPrintf(("commsrp: error %d binding socket\n", iErr)); return(COMM_UNEXPECTED); } // setup target info SockaddrInit(&ref->peeraddr, AF_INET); SockaddrInSetAddr(&ref->peeraddr, uAddr); SockaddrInSetPort(&ref->peeraddr, iConnPort); // setup for callbacks SocketCallback(ref->socket, CALLB_RECV, 100, ref, &_CommSRPEvent); // change the state ref->state = ST_CONN; return(0); }