/*H********************************************************************************/ /*! \File connapi.c \Description ConnApi is a high-level connection manager, that packages the "connect to peer" process into a single module. Both game connections and voice connections can be managed. Multiple peers are supported in a host/client model for the game connection, and a peer/peer model for the voice connections. \Copyright Copyright (c) Electronic Arts 2005. ALL RIGHTS RESERVED. \Notes About ConnApi using ProtoMangle: For plaforms other than xboxone: * ConnApi uses ProtoMangle to exercice connection demangling logic with a central demangler service. * When demangler is enabled, a typical connection state transition is: success: INIT --> CONN --> ACTV success after demangling: INIT --> CONN --> MNGL --> INIT --> CONN --> ACTV * For each connection, the boolean ConnApiConnInfoT::bDemangling is used to track "demangling being in progress" across the "MNGL --> INIT --> CONN --> ACTV" state sequence such that ProtoMangleReport() gets called appropriately once post-demangling conn attempt finishes. * Additionally, the boolean ConnApiConnInfoT::bDemangling is used to "serialize" demangling attempts as interaction with the demangler is limited to one session at a time. For xboxone: * The central demangling service is never used, but the secure device association creation is mapped to the protomangle metaphore. * A typical connection state transition is: p2p conn (SecureAssoc needed): MNGL --> INIT --> CONN --> ACTV server conn (SecureAssoc not needed): INIT --> CONN --> ACTV * For each connection, the boolean ConnApiConnInfoT::bDemangling no longer means "demangling being in progress" across states. It's rather used in such way that, for server conns, if the conn attempt fails, then the MGNL state is not entered. \Version 01/04/2005 (jbrookes) First Version */ /********************************************************************************H*/ /*** Include files ****************************************************************/ #include #include #include "DirtySDK/dirtysock.h" #include "DirtySDK/dirtysock/dirtynames.h" #include "DirtySDK/dirtysock/dirtymem.h" #include "DirtySDK/proto/protomangle.h" #include "DirtySDK/proto/prototunnel.h" #include "DirtySDK/voip/voip.h" #include "DirtySDK/voip/voipgroup.h" #include "DirtySDK/game/connapi.h" /*** Defines **********************************************************************/ //! define if we're using Xbox networking #if defined(DIRTYCODE_XBOXONE) && !defined(DIRTYCODE_GDK) #define CONNAPI_XBOX_NETWORKING 1 #else #define CONNAPI_XBOX_NETWORKING 0 #endif //! default connapi timeout #define CONNAPI_TIMEOUT_DEFAULT (15*1000) //! connapi connection timeout #define CONNAPI_CONNTIMEOUT_DEFAULT (10*1000) //! connapi default demangler timeout (per user) #if CONNAPI_XBOX_NETWORKING #define CONNAPI_DEMANGLER_TIMEOUT (2*CONNAPI_CONNTIMEOUT_DEFAULT) // experimentation showed that creation of security association can be significantly long #define CONNAPI_DEMANGLER_WITH_FAILOVER_TIMEOUT (CONNAPI_CONNTIMEOUT_DEFAULT) // however for scenarios involving CC assistance, we don't wait that long #else #define CONNAPI_DEMANGLER_TIMEOUT (CONNAPI_CONNTIMEOUT_DEFAULT) #define CONNAPI_DEMANGLER_WITH_FAILOVER_TIMEOUT (CONNAPI_DEMANGLER_TIMEOUT) #endif //! default GameLink buffer size #define CONNAPI_LINKBUFDEFAULT (1024) //! test demangling #define CONNAPI_DEMANGLE_TEST (DIRTYCODE_DEBUG && FALSE) //! random demangle local port? #define CONNAPI_RNDLCLDEMANGLEPORT (0) // used to be enabled for WII revolution platform //! max number of registered callbacks #define CONNAPI_MAX_CALLBACKS (8) //! connapi client flags #define CONNAPI_CLIENTFLAG_REMOVE (1) // remove client from clientlist #define CONNAPI_CLIENTFLAG_TUNNELPORTDEMANGLED (2) // tunnel port has been demangled #define CONNAPI_CLIENTFLAG_SECUREADDRRESOLVED (4) // secure address has been resolved (xboxone-specific) //! debug flags #define CONNAPI_CLIENTFLAG_P2PFAILDBG (128) // set to force P2P connections to fail /*** Type Definitions *************************************************************/ struct ConnApiRefT { //! connapi user callback info ConnApiCallbackT *pCallback[CONNAPI_MAX_CALLBACKS]; void *pUserData[CONNAPI_MAX_CALLBACKS]; //! dirtymem memory group int32_t iMemGroup; void *pMemGroupUserData; //! game port to connect on uint16_t uGamePort; //! voip port to connect on uint16_t uVoipPort; //! game connection flags uint16_t uConnFlags; //! netmask, used for external address comparisons uint32_t uNetMask; //! game name char strGameName[32]; //! game link buffer size int32_t iLinkBufSize; //! master game util ref (used for advertising) NetGameUtilRefT *pGameUtilRef; //! protomangle ref ProtoMangleRefT *pProtoMangle; //! prototunnel ref ProtoTunnelRefT *pProtoTunnel; //! prototunnel port int32_t iTunnelPort; //! do we own tunnel ref? int32_t bTunnelOwner; //! protomangle server name char strDemanglerServer[48]; //! voip ref VoipRefT *pVoipRef; //! voipgroup ref VoipGroupRefT *pVoipGroupRef; //! comm construct function CommAllConstructT *pCommConstruct; //! our address DirtyAddrT SelfAddr; //! our unique identifier uint32_t uSelfId; //! index of ourself in client list int32_t iSelf; //! session identifier int32_t iSessId; //! connection timeout value int32_t iConnTimeout; //! timeout value int32_t iTimeout; //! demangler timeouts int32_t iConfigMnglTimeout; // configured timeout for cases where CC assistance are not applicable int32_t iConfigMnglTimeoutFailover; // configured timeout for cases where CC assistance are applicable int32_t iCurrentMnglTimeout; // effective timeout value to be passed down to ProtoMangle //! gamelink configuration - input packet queue size int32_t iGameMinp; //! gamelink configuration - output packet queue size int32_t iGameMout; //! gamelink configuration - max packet width int32_t iGameMwid; //! gamelink configuration - unacknowledged packet window int32_t iGameUnackLimit; //! Connection Concierge mode (CONNAPI_CCMODE_*) int32_t iCcMode; //! session information char strSession[128]; //! demangler reporting? uint8_t bReporting; //! is demangler enabled? uint8_t bDemanglerEnabled; //! is tunnel enabled? uint8_t bTunnelEnabled; //! inside of a callback? uint8_t bInCallback; //! disc callback on client removal? uint8_t bRemoveCallback; //! auto-update enabled? uint8_t bAutoUpdate; #if CONNAPI_XBOX_NETWORKING //! 'exsn', the external session name - passed to the demangler char strExternalSessionName[128]; //! 'estn', the external session template name - passed to the demangler char strExternalSessionTemplateName[128]; //! 'scid', the service configuration id - passed to the demangler char strScid[128]; #endif //! 'adve', TRUE if ProtoAdvt advertising enabled uint8_t bDoAdvertising; //! 'meta', TRUE if commudp metadata enabled uint8_t bCommUdpMetadata; //! game socket ref, if available uintptr_t uGameSockRef; //! voip socket ref, if available uintptr_t uVoipSockRef; //! tunnel socket ref, if available uintptr_t uTunlSockRef; //! game host int32_t iGameHostIndex; //! voip host int32_t iVoipHostIndex; #if DIRTYCODE_DEBUG //! force direct connection to fail ? uint8_t bFailP2PConnect; #endif uint32_t uGameTunnelFlag; uint32_t uGameTunnelFlagOverride; int32_t iQosDuration; int32_t iQosInterval; int32_t iQosPacketSize; //! client state enum { ST_IDLE, //!< idle ST_INGAME //!< hosting or joined a game } eState; //! game network topology ConnApiGameTopologyE eGameTopology; //! voip network topology ConnApiVoipTopologyE eVoipTopology; //! client list - must come last in ref as it is variable length ConnApiClientListT ClientList; }; /*** Variables ********************************************************************/ //! number of voipgroups we allocate in the manager static int8_t _ConnApi_iMaxVoipGroups = 8; /*** Private Functions ************************************************************/ /*F********************************************************************************/ /*! \Function _ConnApiDefaultCallback \Description Default ConnApi user callback. On a debug build, displays state transition information. \Input *pConnApi - connection manager ref \Input *pCbInfo - connection info \Input *pUserData - user callback data \Version 01/17/2005 (jbrookes) */ /********************************************************************************F*/ static void _ConnApiDefaultCallback(ConnApiRefT *pConnApi, ConnApiCbInfoT *pCbInfo, void *pUserData) { #if DIRTYCODE_LOGGING static const char *_StateNames[CONNAPI_NUMSTATUSTYPES] = { "CONNAPI_STATUS_INIT", "CONNAPI_STATUS_CONN", "CONNAPI_STATUS_MNGL", "CONNAPI_STATUS_ACTV", "CONNAPI_STATUS_DISC" }; static const char *_TypeNames[CONNAPI_NUMCBTYPES] = { "CONNAPI_CBTYPE_GAMEEVENT", "CONNAPI_CBTYPE_DESTEVENT", "CONNAPI_CBTYPE_VOIPEVENT", }; // display state change NetPrintf(("connapi: [%p] client %d) [%s] %s -> %s\n", pConnApi, pCbInfo->iClientIndex, _TypeNames[pCbInfo->eType], _StateNames[pCbInfo->eOldStatus], _StateNames[pCbInfo->eNewStatus])); #endif } #if CONNAPI_RNDLCLDEMANGLEPORT /*F********************************************************************************/ /*! \Function _ConnApiGenerateDemanglePort \Description Generate a "random" demangle port to use, actually generated based on the two lowest bytes of the MAC address and multiplied by a small prime number, and then modded into a small range of ports. The intent here is to generate ports that will both not be reused on successive attempts and also be unlikely to collide with other client choices, in case there are other clients operating the same code behind the same NAT device. \Input *pConnApi - connection manager ref \Output uint16_t - generated demangle port to use \Version 03/02/2009 (jbrookes) */ /********************************************************************************F*/ static uint16_t _ConnApiGenerateDemanglePort(ConnApiRefT *pConnApi) { static const uint16_t _uPortBase = 10000; static const uint16_t _uPortRange = 1000; static uint16_t _uPortOffset = 0xffff; // initialize port offset based on mac address to avoid collisions with other clients behind the same NAT if (_uPortOffset == 0xffff) { uint8_t aMacAddr[6]; if (NetConnStatus('macx', 0, aMacAddr, sizeof(aMacAddr)) >= 0) { // generate mac-based port within specified range NetPrintf(("connapi: [%p] generating port offset based on MAC addr '%s'\n", pConnApi, NetConnMAC())); _uPortOffset = aMacAddr[4]; _uPortOffset = (_uPortOffset << 8) + aMacAddr[5]; _uPortOffset *= 7; } else { NetPrintf(("connapi: [%p] unable to acquire mac address\n", pConnApi)); _uPortOffset = 0; } } // generate new port _uPortOffset = (_uPortOffset + 1) % _uPortRange; return(_uPortBase + _uPortOffset); } #endif #if DIRTYCODE_LOGGING /*F********************************************************************************/ /*! \Function _ConnApiDisplayClientInfo \Description Debug-only function to print the given client info to debug output. \Input *pClient - pointer to client to display \Version 01/06/2005 (jbrookes) */ /********************************************************************************F*/ static void _ConnApiDisplayClientInfo(ConnApiClientT *pClient, int32_t iClient) { if (pClient->bAllocated) { NetPrintf(("connapi: %d) id:0x%08x lid:0x%08x rid:0x%08x ip:%a hosted:%s qos:%s dirtyaddr:%s\n", iClient, pClient->ClientInfo.uId, pClient->ClientInfo.uLocalClientId, pClient->ClientInfo.uRemoteClientId, pClient->ClientInfo.uAddr, pClient->ClientInfo.bIsConnectivityHosted ? "TRUE" : "FALSE", pClient->ClientInfo.bEnableQos ? "TRUE" : "FALSE", pClient->ClientInfo.DirtyAddr.strMachineAddr)); } else { NetPrintf(("connapi: %d) empty\n", iClient)); } } #endif #if DIRTYCODE_LOGGING /*F********************************************************************************/ /*! \Function _ConnApiDisplayClientList \Description Debug-only function to print the given client list to debug output. \Input *pClientList - pointer to client list to display \Version 01/06/2005 (jbrookes) */ /********************************************************************************F*/ static void _ConnApiDisplayClientList(ConnApiClientListT *pClientList) { int32_t iClient; NetPrintf(("connapi: clientlist display\n")); for (iClient = 0; iClient < pClientList->iMaxClients; iClient++) { _ConnApiDisplayClientInfo(&pClientList->Clients[iClient], iClient); } } #endif /*F********************************************************************************/ /*! \Function _ConnApiClientReleaseProtoMangleResources \Description When both voip and game are in DISC state, let ProtoMangle know that it can release resources associated with this client. \Input *pConnApi - pointer to module state \Input *pClient - pointer to client to init \Notes 1- Really needed for Xbox One only. Ends up being a no-op on other platform. 2- For dirtycast-based scenarios, the client entry for the gameserver always has pClient->VoipInfo.eStatus = CONNAPI_STATUS_INIT, so the call to ProtoMangleContro() is never exercised. \Version 09/13/2013 (mclouatre) */ /********************************************************************************F*/ static void _ConnApiClientReleaseProtoMangleResources(ConnApiRefT *pConnApi, ConnApiClientT *pClient) { if ((pConnApi->bDemanglerEnabled == TRUE) && (pClient->GameInfo.eStatus == CONNAPI_STATUS_DISC) && (pClient->VoipInfo.eStatus == CONNAPI_STATUS_DISC)) { ProtoMangleControl(pConnApi->pProtoMangle, 'remv', (int32_t)(pClient - pConnApi->ClientList.Clients), 0, NULL); } } #if CONNAPI_XBOX_NETWORKING /*F********************************************************************************/ /*! \Function _ConnApiInitClientConnectionState \Description Initialize client's game and voip connection states based on selected game and voip topology. \Input *pConnApi - connection manager ref \Input *pClient - pointer to client to init \Input iClientIndex - index of client \Input uConnMode - bit mask to specify what connection state needs to be initialized (CONNAPI_CONNFLAG_XXXCONN) \Version 11/08/2013 (mclouatre) */ /********************************************************************************F*/ static void _ConnApiInitClientConnectionState(ConnApiRefT *pConnApi, ConnApiClientT *pClient, int32_t iClientIndex, uint32_t uConnMode) { /* $todo - revisit this logics to make it more explicit --> p2p game connections that do not need to be demangled are skipped in _ConnApiUpdateConnections() because they don't have the CONNAPI_CONNFLAG_GAMECONN flag set --> the same trick is not feasible with p2p voip connections because they need to reach the CONNAPI_STATUS_ACTV state even if routed through a server; so we make sure they do not get demangled by defaulting straight to CONNAPI_STATUS_INIT here. */ if (uConnMode & CONNAPI_CONNFLAG_GAMECONN) { // if we're a xboxone client in phxc mode and we are currently dealing with the dedicated server client entry or we are connectivity hosted, // then make sure we do not attempt to demangle with protomanglexboxone if (!pConnApi->bDemanglerEnabled || pClient->ClientInfo.bIsConnectivityHosted || (iClientIndex == pConnApi->iSelf) || ((pConnApi->eGameTopology == CONNAPI_GAMETOPOLOGY_SERVERHOSTED) && (iClientIndex == pConnApi->iGameHostIndex))) { pClient->GameInfo.eStatus = CONNAPI_STATUS_INIT; pClient->GameInfo.bDemangling = TRUE; // make sure demangling will not be attempted if first connection attempt to server fails } else { pClient->GameInfo.eStatus = CONNAPI_STATUS_MNGL; // if secure address is already known, don't clear it - it will be reused if ((pClient->uFlags & CONNAPI_CLIENTFLAG_SECUREADDRRESOLVED) == 0) { pClient->ClientInfo.uAddr = 0; pClient->ClientInfo.uLocalAddr = 0; } } } if (uConnMode & CONNAPI_CONNFLAG_VOIPCONN) { // if voip is routed through a dedicated server, game is routed through a dedicated server but voip is not or when connectivity hosted, then make sure we do // not attempt to demangle voip connections with protomanglexboxone. There is no need to check to see if the client is a voip topology host (host will never have conn mode CONNAPI_CONNFLAG_VOIPCONN) if (!pConnApi->bDemanglerEnabled || pClient->ClientInfo.bIsConnectivityHosted || (iClientIndex == pConnApi->iSelf) || (pConnApi->eVoipTopology == CONNAPI_VOIPTOPOLOGY_SERVERHOSTED) || ((pConnApi->eGameTopology == CONNAPI_GAMETOPOLOGY_SERVERHOSTED) && (iClientIndex == pConnApi->iGameHostIndex))) { pClient->VoipInfo.eStatus = CONNAPI_STATUS_INIT; pClient->VoipInfo.bDemangling = TRUE; // make sure demangling will not be attempted if first connection attempt to server fails } else { pClient->VoipInfo.eStatus = CONNAPI_STATUS_MNGL; // if secure address is already known, don't clear it - it will be reused if ((pClient->uFlags & CONNAPI_CLIENTFLAG_SECUREADDRRESOLVED) == 0) { pClient->ClientInfo.uAddr = 0; pClient->ClientInfo.uLocalAddr = 0; } } } } #endif /*F********************************************************************************/ /*! \Function _ConnApiInitClient \Description Initialize a single client based on input user info. \Input *pConnApi - pointer to module state \Input *pClient - pointer to client to init \Input *pClientInfo - pointer to user info to init client with \Input iClientIndex - index of client \Version 01/06/2005 (jbrookes) */ /********************************************************************************F*/ static void _ConnApiInitClient(ConnApiRefT *pConnApi, ConnApiClientT *pClient, ConnApiClientInfoT *pClientInfo, int32_t iClientIndex) { // initialize new client structure and save input user info ds_memclr(pClient, sizeof(*pClient)); ds_memcpy_s(&pClient->ClientInfo, sizeof(pClient->ClientInfo), pClientInfo, sizeof(*pClientInfo)); // initialize default voip values pClient->iVoipConnId = VOIP_CONNID_NONE; // set up remote (connect) port info pClient->GameInfo.uMnglPort = (pClient->ClientInfo.uGamePort == 0) ? pConnApi->uGamePort : pClient->ClientInfo.uGamePort; pClient->VoipInfo.uMnglPort = (pClient->ClientInfo.uVoipPort == 0) ? pConnApi->uVoipPort : pClient->ClientInfo.uVoipPort; // set up local (bind) port info pClient->GameInfo.uLocalPort = ((pClient->ClientInfo.uLocalGamePort == 0) || (pConnApi->bTunnelEnabled == TRUE)) ? pConnApi->uGamePort : pClient->ClientInfo.uLocalGamePort; pClient->VoipInfo.uLocalPort = ((pClient->ClientInfo.uLocalVoipPort == 0) || (pConnApi->bTunnelEnabled == TRUE)) ? pConnApi->uVoipPort : pClient->ClientInfo.uLocalVoipPort; // set unique client identifier if not already supplied if (pClient->ClientInfo.uId == 0) { pClient->ClientInfo.uId = (uint32_t)iClientIndex + 1; } if ((pClient->ClientInfo.bEnableQos == TRUE) && (iClientIndex != pConnApi->iSelf)) { NetPrintf(("connapi: [%p] delaying voip connection for player %d because of QoS validation\n", pConnApi, iClientIndex)); } else { // Voip is not delayed when bEnableQos is FALSE pClient->bEstablishVoip = TRUE; } #if CONNAPI_XBOX_NETWORKING _ConnApiInitClientConnectionState(pConnApi, pClient, iClientIndex, CONNAPI_CONNFLAG_GAMECONN|CONNAPI_CONNFLAG_VOIPCONN); #endif // mark as allocated pClient->bAllocated = TRUE; } /*F********************************************************************************/ /*! \Function _ConnApiUpdateClientFlags \Description Update client flags based on game mode and game flags. \Input *pConnApi - pointer to module state \Version 05/16/2005 (jbrookes) */ /********************************************************************************F*/ static void _ConnApiUpdateClientFlags(ConnApiRefT *pConnApi) { ConnApiClientT *pClient; int32_t iClient; for (iClient = 0; iClient < pConnApi->ClientList.iMaxClients; iClient++) { pClient = &pConnApi->ClientList.Clients[iClient]; if (!pClient->bAllocated || (iClient == pConnApi->iSelf)) { continue; } pClient->uConnFlags = pConnApi->uConnFlags; // when not in a peer web game topology, only establish a game connection to the host if ((iClient != pConnApi->iGameHostIndex) && (pConnApi->iGameHostIndex != pConnApi->iSelf) && (pConnApi->eGameTopology != CONNAPI_GAMETOPOLOGY_PEERWEB)) { pClient->uConnFlags &= ~CONNAPI_CONNFLAG_GAMECONN; } if (pConnApi->eVoipTopology == CONNAPI_VOIPTOPOLOGY_SERVERHOSTED) { // when in a server hosted voip topology, don't establish a voip connection to the host if (iClient == pConnApi->iVoipHostIndex) { pClient->uConnFlags &= ~CONNAPI_CONNFLAG_VOIPCONN; } } else { /* if we are in a game with a server hosted and the voip topology is not server hosted, don't establish connections to that host */ if ((pConnApi->eGameTopology == CONNAPI_GAMETOPOLOGY_SERVERHOSTED) && (iClient == pConnApi->iGameHostIndex)) { pClient->uConnFlags &= ~CONNAPI_CONNFLAG_VOIPCONN; } } // if voip needs to be disabled while we do QoS measurements, update the conn flags accordingly if (pClient->bEstablishVoip == FALSE) { pClient->uConnFlags &= ~CONNAPI_CONNFLAG_VOIPCONN; } } } /*F********************************************************************************/ /*! \Function _ConnApiGenerateSessionKey \Description Generate session key for demangling session. \Input *pConnApi - pointer to module state \Input *pClient - pointer to peer \Input iClientIndex - index of peer \Input *pSess - [out] pointer to session buffer \Input iSessSize - size of session buffer \Input *pSessType - type of session - "game" or "voip" \Version 01/13/2005 (jbrookes) */ /********************************************************************************F*/ static void _ConnApiGenerateSessionKey(ConnApiRefT *pConnApi, ConnApiClientT *pClient, int32_t iClientIndex, char *pSess, int32_t iSessSize, const char *pSessType) { uint32_t uIdA, uIdB, uTemp; uIdA = pClient->ClientInfo.uLocalClientId; uIdB = pClient->ClientInfo.uRemoteClientId; if (uIdB < uIdA) { uTemp = uIdA; uIdA = uIdB; uIdB = uTemp; } ds_snzprintf(pSess, iSessSize, "%08x-%08x-%s-%08x", uIdA, uIdB, pSessType, pConnApi->iSessId); } /*F********************************************************************************/ /*! \Function _ConnApiTunnelAlloc \Description Allocate a tunnel for the given client. \Input *pConnApi - pointer to module state \Input *pClient - client to connect to \Input iClientIndex - index of client \Input uRemoteAddr - remote address to tunnel to \Input bLocalAddr - TRUE if we are using local address, else FALSE \Version 12/20/2005 (jbrookes) */ /********************************************************************************F*/ static void _ConnApiTunnelAlloc(ConnApiRefT *pConnApi, ConnApiClientT *pClient, int32_t iClientIndex, uint32_t uRemoteAddr, uint8_t bLocalAddr) { ProtoTunnelInfoT TunnelInfo; // if connectivity is hosted and if the tunnel to the hosting server was already created in the connection flow of another client // then skip tunnel creation and reuse that tunnel if (pClient->ClientInfo.bIsConnectivityHosted) { int32_t iClient; for (iClient = 0; iClient < pConnApi->ClientList.iMaxClients; iClient++) { if ( (pClient != &pConnApi->ClientList.Clients[iClient]) && (pClient->ClientInfo.uHostingServerId == pConnApi->ClientList.Clients[iClient].ClientInfo.uHostingServerId) && (pConnApi->ClientList.Clients[iClient].iTunnelId != 0) ) { pClient->iTunnelId = pConnApi->ClientList.Clients[iClient].iTunnelId; NetPrintf(("connapi: [%p] client %d is reusing tunnel 0x%08x to hosting server 0x%08x\n", pConnApi, iClientIndex, pClient->iTunnelId, pClient->ClientInfo.uHostingServerId)); return; } } } // set up tunnel info ds_memclr(&TunnelInfo, sizeof(TunnelInfo)); TunnelInfo.uRemoteClientId = pClient->ClientInfo.bIsConnectivityHosted ? pClient->ClientInfo.uHostingServerId : pClient->ClientInfo.uRemoteClientId; TunnelInfo.uRemoteAddr = uRemoteAddr; TunnelInfo.uRemotePort = bLocalAddr ? pClient->ClientInfo.uLocalTunnelPort : pClient->ClientInfo.uTunnelPort; TunnelInfo.aRemotePortList[0] = pClient->GameInfo.uMnglPort; TunnelInfo.aRemotePortList[1] = pClient->VoipInfo.uMnglPort; TunnelInfo.aPortFlags[0] = (pConnApi->uGameTunnelFlagOverride) ? (pConnApi->uGameTunnelFlag) : (PROTOTUNNEL_PORTFLAG_ENCRYPTED|PROTOTUNNEL_PORTFLAG_AUTOFLUSH); TunnelInfo.aPortFlags[1] = PROTOTUNNEL_PORTFLAG_ENCRYPTED; NetPrintf(("connapi: [%p] setting client %d clientId=0x%08x TunnelInfo.uRemotePort %d %s\n", pConnApi, iClientIndex, TunnelInfo.uRemoteClientId, TunnelInfo.uRemotePort, pClient->ClientInfo.bIsConnectivityHosted ? "(hosted connection)" : "(direct connection)")); // allocate tunnel, set the local client id and return to caller pClient->iTunnelId = ProtoTunnelAlloc(pConnApi->pProtoTunnel, &TunnelInfo, pClient->ClientInfo.strTunnelKey); if (pClient->iTunnelId >= 0) { ProtoTunnelControl(pConnApi->pProtoTunnel, 'tcid', pClient->iTunnelId, (int32_t)pClient->ClientInfo.uLocalClientId, NULL); } } /*F********************************************************************************/ /*! \Function _ConnApiVoipTunnelAlloc \Description Allocate a tunnel for voip for the given client depending on voip settings. \Input *pConnApi - pointer to module state \Input *pClient - client to connect to \Input iClientIndex - index of client \Input uRemoteAddr - remote address to tunnel to \Input bLocalAddr - TRUE if we are using local address, else FALSE \Version 12/23/2008 (jrainy) */ /********************************************************************************F*/ static void _ConnApiVoipTunnelAlloc(ConnApiRefT *pConnApi, ConnApiClientT *pClient, int32_t iClientIndex, uint32_t uRemoteAddr, uint8_t bLocalAddr) { int32_t iTunnelId = 0; // if doing redirect via host, check for previously created tunnel for re-use if (pConnApi->eVoipTopology == CONNAPI_VOIPTOPOLOGY_SERVERHOSTED) { iTunnelId = pConnApi->ClientList.Clients[pConnApi->iVoipHostIndex].iTunnelId; } // if no reused tunnel, create one if (iTunnelId == 0) { _ConnApiTunnelAlloc(pConnApi, pClient, iClientIndex, uRemoteAddr, bLocalAddr); } else { pClient->iTunnelId = iTunnelId; } } /*F********************************************************************************/ /*! \Function _ConnApiTunnelFree \Description Free the given client's tunnel. \Input *pConnApi - pointer to module state \Input *pClient - pointer to client to allocate tunnel for \Version 12/20/2005 (jbrookes) */ /********************************************************************************F*/ static void _ConnApiTunnelFree(ConnApiRefT *pConnApi, ConnApiClientT *pClient) { // tunnel active? if (!pConnApi->bTunnelEnabled) { return; } // if voip to this client is redirected via the host in a C/S game, // and we're trying to free a client, but not the host itself, skip tunnel destruction. if (pConnApi->eVoipTopology == CONNAPI_VOIPTOPOLOGY_SERVERHOSTED) { if ((pConnApi->ClientList.Clients[pConnApi->iVoipHostIndex].iTunnelId == pClient->iTunnelId) && (&pConnApi->ClientList.Clients[pConnApi->iVoipHostIndex] != pClient)) { pClient->iTunnelId = 0; return; } } // if connectivity is hosted and if the tunnel to the hosting server is still used for another client // then skip tunnel destruction if (pClient->ClientInfo.bIsConnectivityHosted) { int32_t iClient; for (iClient = 0; iClient < pConnApi->ClientList.iMaxClients; iClient++) { if ( (pClient != &pConnApi->ClientList.Clients[iClient]) && (pClient->ClientInfo.uHostingServerId == pConnApi->ClientList.Clients[iClient].ClientInfo.uHostingServerId) && (pClient->iTunnelId == pConnApi->ClientList.Clients[iClient].iTunnelId) ) { #if DIRTYCODE_LOGGING ConnApiClientT *pFirstClient = &pConnApi->ClientList.Clients[0]; NetPrintf(("connapi: [%p] freeing tunnel 0x%08x to hosting server 0x%08x is skipped for client %d because tunnel still used by at least client %d\n", pConnApi, pClient->iTunnelId, pClient->ClientInfo.uHostingServerId, (pClient - pFirstClient), iClient)); #endif pClient->iTunnelId = 0; return; } } } if (pClient->iTunnelId != 0) { ProtoTunnelFree2(pConnApi->pProtoTunnel, pClient->iTunnelId, pClient->ClientInfo.strTunnelKey, pClient->ClientInfo.uAddr); pClient->iTunnelId = 0; } } /*F********************************************************************************/ /*! \Function _ConnApiGetConnectAddr \Description Selects between internal and internal address to use for connection, based on whether the external addresses are equal or not. \Input *pConnApi - pointer to module state \Input *pClient - pointer to client to connect to \Input *pLocalAddr - [out] storage for boolean indicating whether local address was used or not \Input uConnMode - game conn or voip conn \Input **pClientUsedRet - the pointer to use to return the client actually used \Output int32_t - ip address to be used to connnect to the specified client \Version 01/11/2005 (jbrookes) */ /********************************************************************************F*/ static int32_t _ConnApiGetConnectAddr(ConnApiRefT *pConnApi, ConnApiClientT *pClient, uint8_t *pLocalAddr, const uint32_t uConnMode, ConnApiClientT **pClientUsedRet) { ConnApiClientT *pSelf; int32_t uAddr; uint8_t bLocalAddr = FALSE; ConnApiClientT *pClientUsed = NULL; #if DIRTYCODE_LOGGING const char *pConn = (uConnMode == CONNAPI_CONNFLAG_GAMECONN) ? "game" : "voip"; #endif // ref local client info pSelf = &pConnApi->ClientList.Clients[pConnApi->iSelf]; if ((uConnMode == CONNAPI_CONNFLAG_VOIPCONN) && (pConnApi->eVoipTopology == CONNAPI_VOIPTOPOLOGY_SERVERHOSTED)) { uAddr = pConnApi->ClientList.Clients[pConnApi->iVoipHostIndex].ClientInfo.uAddr; pClientUsed = &pConnApi->ClientList.Clients[pConnApi->iVoipHostIndex]; NetPrintf(("connapi: [%s] using host address to connect to (or disconnect from) client 0x%08x\n", pConn, pClient->ClientInfo.uId)); } else { if (pClient->ClientInfo.uAddr == 0) { /* we get into passive mode when we don't have an address for our peer and we are expecting the incoming traffic to help us choose the correct remote address. for clarity of how this works, we can look at the behavior of commudp, given that the game traffic is what comes first in the establishing the connections. when commudp sees that the address is 0.0.0.0, it will normally not send POKE packets to the peer. when the INIT packets come in the remote address gets updated based on the source of the traffic. after which point we can complete the connection with our peer having the updated address. NOTE: when we introduced prototunnel to the equation the passive functionality was broken because it using a virtual address, not understanding what the real address is. this means that we will send invalid POKE packets to 0.0.0.0 via the tunnel which can be seen as errors in the logs. when we receive incoming traffic the connection can still complete successfully using the same logic explained above. this behavior is only expected in a connection between an xbox client (using xbox secure networking) and dedicated server (from the standpoint of the server). if this behavior is seen anywhere else there is a problem in the assigning of address to connapi. dirtysock will recover as long as one side has a correct address. if both sides have invalid addresses you could expect the connection to fail */ NetPrintf(("connapi: [%s] using %a address to connect to (or disconnect from) client 0x%08x in passive mode\n", pConn, pClient->ClientInfo.uAddr, pClient->ClientInfo.uId)); uAddr = pClient->ClientInfo.uAddr; pClientUsed = pClient; } // if external addresses match, use local address else if ((pSelf->ClientInfo.uAddr & pConnApi->uNetMask) == (pClient->ClientInfo.uAddr & pConnApi->uNetMask)) { NetPrintf(("connapi: [%s] using local address to connect to (or disconnect from) client 0x%08x\n", pConn, pClient->ClientInfo.uId)); uAddr = pClient->ClientInfo.uLocalAddr; pClientUsed = pClient; bLocalAddr = TRUE; } else { NetPrintf(("connapi: [%s] using peer address to connect to (or disconnect from) client 0x%08x\n", pConn, pClient->ClientInfo.uId)); uAddr = pClient->ClientInfo.uAddr; pClientUsed = pClient; } #if DIRTYCODE_DEBUG if (pClient->ClientInfo.bIsConnectivityHosted == FALSE) { if (pConnApi->bFailP2PConnect) { // global P2P fail flag is set NetPrintf(("connapi: [%s] !! P2P CONNECTION FAILURE TRICK !! - global P2P fail - peer address for client 0x%08x replaced with unreachable value\n", pConn, pClient->ClientInfo.uId)); uAddr = 0; } else if (pClient->uFlags & CONNAPI_CLIENTFLAG_P2PFAILDBG) { NetPrintf(("connapi: [%s] !! P2P CONNECTION FAILURE TRICK !! - remote P2P fail flag - peer address for client 0x%08x replaced with unreachable value\n", pConn, pClient->ClientInfo.uId)); uAddr = 0; } else if (pConnApi->ClientList.Clients[pConnApi->iSelf].uFlags & CONNAPI_CLIENTFLAG_P2PFAILDBG) { NetPrintf(("connapi: [%s] !! P2P CONNECTION FAILURE TRICK !! - self P2P fail flag - peer address for client 0x%08x replaced with unreachable value\n", pConn, pClient->ClientInfo.uId)); uAddr = 0; } } #endif } *pLocalAddr = bLocalAddr; if (pClientUsedRet) { *pClientUsedRet = pClientUsed; } return(uAddr); } /*F********************************************************************************/ /*! \Function _ConnApiVoipGroupConnSharingCallback \Description Use to invoke VoipGroupResume() and VoipGroupSuspend when notified by the VoipGroup module. \Input *pVoipGroup - voip group ref \Input eCbType - event identifier \Input iConnId - connection ID \Input *pUserData - user callback data \Input bSending - client sending flag \Input bReceiving - client receiving flag \Version 11/11/2009 (mclouatre) */ /********************************************************************************F*/ static void _ConnApiVoipGroupConnSharingCallback(VoipGroupRefT *pVoipGroup, ConnSharingCbTypeE eCbType, int32_t iConnId, void *pUserData, uint8_t bSending, uint8_t bReceiving) { ConnApiRefT *pConnApi = (ConnApiRefT *)pUserData; ConnApiClientT *pClient = &pConnApi->ClientList.Clients[iConnId]; ConnApiClientT *pClientUsed; int32_t iConnectAddr; uint8_t bLocalAddr; iConnectAddr = _ConnApiGetConnectAddr(pConnApi, pClient, &bLocalAddr, CONNAPI_CONNFLAG_VOIPCONN, &pClientUsed); if (eCbType == VOIPGROUP_CBTYPE_CONNSUSPEND) { NetPrintf(("connapi: [%p] suspending voip connection to client 0x%08x:%a at %d\n", pConnApi, pClient->ClientInfo.uId, iConnectAddr, NetTick())); VoipGroupSuspend(pVoipGroup, iConnId); } else if (eCbType == VOIPGROUP_CBTYPE_CONNRESUME) { // do we have a tunnel to this client? if (pClientUsed->iTunnelId > 0) { NetPrintf(("connapi: [%p] we have a tunnel for client %d; using virtual address %a\n", pConnApi, iConnId, pClientUsed->iTunnelId)); iConnectAddr = pClientUsed->iTunnelId; } NetPrintf(("connapi: [%p] resuming voip connection to client 0x%08x:%a at %d\n", pConnApi, pClient->ClientInfo.uId, iConnectAddr, NetTick())); VoipGroupResume(pVoipGroup, iConnId, iConnectAddr, pClient->VoipInfo.uMnglPort, pClient->VoipInfo.uLocalPort, pClient->ClientInfo.uId, pConnApi->iSessId, pClient->ClientInfo.bIsConnectivityHosted); #if DIRTYCODE_LOGGING if (bSending != bReceiving) { NetPrintf(("connapi: [%p] warning - send and receive mute flags are different for client 0x%08x:%a\n", pConnApi, pClient->ClientInfo.uId, iConnectAddr)); } #endif VoipGroupMuteByConnId(pVoipGroup, iConnId, bSending); } else { NetPrintf(("connapi: [%p] critical error - unknown connection sharing event type\n", pConnApi)); } } /*F********************************************************************************/ /*! \Function _ConnApiGetConnectParms \Description Gets connection parameters. \Input *pConnApi - pointer to module state \Input *pClient - pointer to client to connect to \Input *pConnName - [out] storage for connection name \Input iNameSize - size of output buffer \Output int32_t - connection flags (NEGAME_CONN_*) \Version 04/21/2005 (jbrookes) */ /********************************************************************************F*/ static int32_t _ConnApiGetConnectParms(ConnApiRefT *pConnApi, ConnApiClientT *pClient, char *pConnName, int32_t iNameSize) { uint32_t uAddrA, uAddrB, uAddrT; uint32_t bHosting = TRUE; int32_t iConnFlags; // reference unique address strings uAddrA = pClient->ClientInfo.uRemoteClientId; uAddrB = pClient->ClientInfo.uLocalClientId; /* determine if we are "hosting" or not, in NetGame terms (one peer must listen and one peer must connect for each connection) */ if (pConnApi->eGameTopology != CONNAPI_GAMETOPOLOGY_PEERWEB) { // if we're client/server, server listens and clients connect bHosting = (pConnApi->iGameHostIndex == pConnApi->iSelf); } else { // if we're peer-web, compare addresses to choose listener and connector bHosting = uAddrA < uAddrB; } /* set up parms based on whether we are "hosting" or not. the connection name is the unique address of the "host" concatenated with the unique address of the "client" */ if (bHosting == TRUE) { // swap names uAddrT = uAddrB; uAddrB = uAddrA; uAddrA = uAddrT; // set conn flags iConnFlags = NETGAME_CONN_LISTEN; } else { iConnFlags = NETGAME_CONN_CONNECT; } // format connection name and return connection flags ds_snzprintf(pConnName, iNameSize, "%x-%x", uAddrA, uAddrB); return(iConnFlags); } /*F********************************************************************************/ /*! \Function _ConnApiUpdateCallback \Description Trigger user callback if the state has changed. \Input *pConnApi - pointer to module state \Input iClientIndex - index of client \Input eType - type of connection (CONNAPI_CBTYPE_*) \Input eOldStatus - previous status \Input eNewStatus - current status \Output int32_t - one=active, zero=disconnected \Version 01/06/2005 (jbrookes) */ /********************************************************************************F*/ static void _ConnApiUpdateCallback(ConnApiRefT *pConnApi, int32_t iClientIndex, ConnApiCbTypeE eType, ConnApiConnStatusE eOldStatus, ConnApiConnStatusE eNewStatus) { ConnApiCbInfoT CbInfo; ConnApiConnInfoT *pConnInfo = NULL; int32_t iIndex; // if no change, no callback if (eOldStatus == eNewStatus) { return; } // otherwise, fire off a callback CbInfo.iClientIndex = iClientIndex; CbInfo.eType = eType; CbInfo.eOldStatus = eOldStatus; CbInfo.eNewStatus = eNewStatus; CbInfo.pClient = &pConnApi->ClientList.Clients[iClientIndex]; if (eType == CONNAPI_CBTYPE_GAMEEVENT) { pConnInfo = (ConnApiConnInfoT *)&CbInfo.pClient->GameInfo; } else if (eType == CONNAPI_CBTYPE_VOIPEVENT) { pConnInfo = (ConnApiConnInfoT *)&CbInfo.pClient->VoipInfo; } // update connection timers if (pConnInfo != NULL) { // finished demangling if (eOldStatus == CONNAPI_STATUS_MNGL) { if (pConnInfo->iConnStart != 0) { #if CONNAPI_XBOX_NETWORKING pConnInfo->ConnTimers.uCreateSATime = NetTickDiff(NetTick(), pConnInfo->iConnStart); #else pConnInfo->ConnTimers.uDemangleTime = NetTickDiff(NetTick(), pConnInfo->iConnStart); #endif } else { #if CONNAPI_XBOX_NETWORKING pConnInfo->ConnTimers.uCreateSATime = 0; #else pConnInfo->ConnTimers.uDemangleTime = 0; #endif } } // it went from some state to active or disconnect log connect time else if ((eOldStatus == CONNAPI_STATUS_CONN) && ((eNewStatus == CONNAPI_STATUS_ACTV) || (eNewStatus == CONNAPI_STATUS_DISC))) { if (pConnInfo->uConnFlags & CONNAPI_CONNFLAG_DEMANGLED) { if (pConnInfo->iConnStart != 0) { pConnInfo->ConnTimers.uDemangleConnectTime = NetTickDiff(NetTick(), pConnInfo->iConnStart); } else { pConnInfo->ConnTimers.uDemangleConnectTime = 0; } } else { if (pConnInfo->iConnStart != 0) { pConnInfo->ConnTimers.uConnectTime = NetTickDiff(NetTick(), pConnInfo->iConnStart); } else { pConnInfo->ConnTimers.uConnectTime = 0; } } } } // call the callback pConnApi->bInCallback = TRUE; _ConnApiDefaultCallback(pConnApi, &CbInfo, NULL); for(iIndex = 0; iIndex < CONNAPI_MAX_CALLBACKS; iIndex++) { if (pConnApi->pCallback[iIndex] != NULL) { pConnApi->pCallback[iIndex](pConnApi, &CbInfo, pConnApi->pUserData[iIndex]); } } pConnApi->bInCallback = FALSE; } /*F********************************************************************************/ /*! \Function _ConnApiDestroyGameConnection \Description Destroy game link to given client. \Input *pConnApi - pointer to module state \Input *pClient - pointer to client to close game connection for \Input iClientIndex - index of client in client array \Input *pReason - reason connection is being closed (for debug use) \Version 01/12/2005 (jbrookes) */ /********************************************************************************F*/ static void _ConnApiDestroyGameConnection(ConnApiRefT *pConnApi, ConnApiClientT *pClient, int32_t iClientIndex, const char *pReason) { // if refs are about to be destroyed, notify application if ((pClient->pGameDistRef != NULL) || (pClient->pGameLinkRef != NULL)) { _ConnApiUpdateCallback(pConnApi, iClientIndex, CONNAPI_CBTYPE_DESTEVENT, CONNAPI_STATUS_ACTV, CONNAPI_STATUS_DISC); } // destroy the refs NetPrintf(("connapi: [%p] destroying game connection to client 0x%08x: %s at %d\n", pConnApi, pClient->ClientInfo.uId, pReason, NetTick())); if (pClient->pGameLinkRef != NULL) { NetGameLinkDestroy(pClient->pGameLinkRef); pClient->pGameLinkRef = NULL; } if (pClient->pGameUtilRef != NULL) { NetGameUtilDestroy(pClient->pGameUtilRef); pClient->pGameUtilRef = NULL; } pClient->GameInfo.eStatus = CONNAPI_STATUS_DISC; _ConnApiClientReleaseProtoMangleResources(pConnApi, pClient); } /*F********************************************************************************/ /*! \Function _ConnApiDestroyVoipConnection \Description Destroy voip link to given client. \Input *pConnApi - pointer to module state \Input *pClient - pointer to client to close game connection for \Input *pReason - reason connection is being closed (for debug use) \Version 01/12/2005 (jbrookes) */ /********************************************************************************F*/ static void _ConnApiDestroyVoipConnection(ConnApiRefT *pConnApi, ConnApiClientT *pClient, const char *pReason) { if (pClient->iVoipConnId >= 0) { NetPrintf(("connapi: destroying voip connection to client 0x%08x: %s at %d\n", pClient->ClientInfo.uId, pReason, NetTick())); VoipGroupDisconnect(pConnApi->pVoipGroupRef, pClient->iVoipConnId); pClient->iVoipConnId = VOIP_CONNID_NONE; } pClient->VoipInfo.eStatus = CONNAPI_STATUS_DISC; _ConnApiClientReleaseProtoMangleResources(pConnApi, pClient); } /*F********************************************************************************/ /*! \Function _ConnApiDisconnectClient \Description Disconnect a client. \Input *pConnApi - pointer to module state \Input *pClient - pointer to client to disconnect \Input iClientIndex - client index \Input *pReason - reason for the close \Version 01/12/2005 (jbrookes) */ /********************************************************************************F*/ static void _ConnApiDisconnectClient(ConnApiRefT *pConnApi, ConnApiClientT *pClient, int32_t iClientIndex, const char *pReason) { _ConnApiDestroyGameConnection(pConnApi, pClient, iClientIndex, pReason); _ConnApiDestroyVoipConnection(pConnApi, pClient, pReason); _ConnApiTunnelFree(pConnApi, pClient); } /*F********************************************************************************/ /*! \Function _ConnApiInitClientList \Description Initialize client list based on input client list. \Input *pConnApi - pointer to module state \Input *pClientList - list of client addresses \Input iNumClients - number of clients in list \Version 01/06/2005 (jbrookes) */ /********************************************************************************F*/ static void _ConnApiInitClientList(ConnApiRefT *pConnApi, ConnApiClientInfoT *pClientList, int32_t iNumClients) { ConnApiClientT *pClient; int32_t iClient; // make sure client count is below max if (iNumClients > pConnApi->ClientList.iMaxClients) { NetPrintf(("connapi: [%p] cannot host %d clients; clamping to %d\n", pConnApi, iNumClients, pConnApi->ClientList.iMaxClients)); iNumClients = pConnApi->ClientList.iMaxClients; } // find our index pConnApi->iSelf = -1; // init so we can check after setup to make sure we're in the list for (iClient = 0, pConnApi->ClientList.iNumClients = 0; iClient < iNumClients; iClient++) { // remember our index in list if (pClientList[iClient].uId == pConnApi->uSelfId) { pConnApi->iSelf = iClient; } } // copy input client list for (iClient = 0, pConnApi->ClientList.iNumClients = 0; iClient < iNumClients; iClient++) { // ref client structure pClient = &pConnApi->ClientList.Clients[iClient]; // need to check to see if the client passed it is valid. if (pClientList[iClient].uId != 0) { // init client structure and copy user info _ConnApiInitClient(pConnApi, pClient, &pClientList[iClient], iClient); // if us, update dirtyaddr if (iClient == pConnApi->iSelf) { // update dirtyaddr and save ref ds_memcpy_s(&pConnApi->SelfAddr, sizeof(pConnApi->SelfAddr), &pClient->ClientInfo.DirtyAddr, sizeof(pClient->ClientInfo.DirtyAddr)); } // increment client count pConnApi->ClientList.iNumClients += 1; } } // make sure iSelf is valid before we continue if (pConnApi->iSelf >= 0) { // ref local client pClient = &pConnApi->ClientList.Clients[pConnApi->iSelf]; // set local user if (pConnApi->pVoipRef != NULL) { VoipGroupControl(pConnApi->pVoipGroupRef, 'clid', pClient->ClientInfo.uId, 0, NULL); } // set prototunnel user if (pConnApi->pProtoTunnel != NULL) { ProtoTunnelControl(pConnApi->pProtoTunnel, 'clid', pClient->ClientInfo.uId, 0, NULL); } // if the voip is server hosted don't ever establish voip from the host if ((pConnApi->eVoipTopology == CONNAPI_VOIPTOPOLOGY_SERVERHOSTED) && (pConnApi->iVoipHostIndex == pConnApi->iSelf)) { pConnApi->uConnFlags &= ~CONNAPI_CONNFLAG_VOIPCONN; } } // set initial client flags _ConnApiUpdateClientFlags(pConnApi); #if DIRTYCODE_LOGGING // make sure we're in the list if (pConnApi->iSelf == -1) { NetPrintf(("connapi: local client 0x%08x not found in client list\n", pConnApi->uSelfId)); } // display client list _ConnApiDisplayClientList(&pConnApi->ClientList); #endif } /*F********************************************************************************/ /*! \Function _ConnApiParseAdvertField \Description Parse a field from an advertisement. \Input *pOutBuf - output buffer for field \Input iOutSize - size of output buffer \Input *pInpBuf - pointer to start of field in advertisement buffer \Input cTerminator - field termination character \Output char * - pointer to next field \Version 01/10/2005 (jbrookes) */ /********************************************************************************F*/ static char *_ConnApiParseAdvertField(char *pOutBuf, int32_t iOutSize, char *pInpBuf, char cTerminator) { char *pEndPtr; pEndPtr = strchr(pInpBuf, cTerminator); *pEndPtr = '\0'; ds_strnzcpy(pOutBuf, pInpBuf, iOutSize); return(pEndPtr+1); } /*F********************************************************************************/ /*! \Function _ConnApiCheckAdvert \Description Scan current adverts for any adverts that are broadcast by clients we are connecting to. \Input *pConnApi - pointer to module state \Version 01/10/2005 (jbrookes) */ /********************************************************************************F*/ static void _ConnApiCheckAdvert(ConnApiRefT *pConnApi) { char strAdvtList[512], *pAdvt, *pTemp; char strName[32], strNote[32], strAddr[32]; ConnApiClientT *pClient; int32_t iAdvt, iNumAdvt, iClient; uint32_t uAdvtId; uint32_t uLocalAddr; // see if there are any advertisements iNumAdvt = NetGameUtilQuery(pConnApi->pGameUtilRef, pConnApi->strGameName, strAdvtList, sizeof(strAdvtList)); NetPrintf(("connapi: [%p] found %d advertisements\n", pConnApi, iNumAdvt)); // parse any advertisements for (pAdvt = strAdvtList, iAdvt = 0; iAdvt < iNumAdvt; iAdvt++) { // extract info from advertisement pAdvt = _ConnApiParseAdvertField(strName, sizeof(strName), pAdvt, '\t'); pAdvt = _ConnApiParseAdvertField(strNote, sizeof(strNote), pAdvt, '\t'); pAdvt = _ConnApiParseAdvertField(strAddr, sizeof(strAddr), pAdvt+4, '\n'); sscanf(strName, "%u", &uAdvtId); // extract address from addr field pTemp = strchr(strAddr, ':'); *pTemp = '\0'; uLocalAddr = SocketInTextGetAddr(strAddr); // does the name match one of our client's names? for (iClient = 0; iClient < pConnApi->ClientList.iMaxClients; iClient++) { pClient = &pConnApi->ClientList.Clients[iClient]; if ((uAdvtId == pClient->ClientInfo.uId) && (pClient->ClientInfo.uLocalAddr != uLocalAddr)) { NetPrintf(("connapi: updating local address of machine Id %u from %a to %a\n", uAdvtId, pClient->ClientInfo.uAddr, uLocalAddr)); pClient->ClientInfo.uLocalAddr = uLocalAddr; } } } } /*F********************************************************************************/ /*! \Function _ConnApiSetGamelinkOpt \Description Set a gamelink option, if it isn't the default. \Input *pUtilRef - pointer to util ref for game link \Input iOpt - gamelink option to set \Input iValue - value to set \Version 06/03/2005 (jbrookes) */ /********************************************************************************F*/ static void _ConnApiSetGamelinkOpt(NetGameUtilRefT *pUtilRef, int32_t iOpt, int32_t iValue) { if (iValue != 0) { NetGameUtilControl(pUtilRef, iOpt, iValue); } } /*F********************************************************************************/ /*! \Function _ConnApiDemangleReport \Description Initiate a demangler report to indicate connection success or failure. \Input *pConnApi - pointer to module state \Input *pInfo - connection info \Input eStatus - connection result (success/fail) \Version 01/17/2005 (jbrookes) */ /********************************************************************************F*/ static void _ConnApiDemangleReport(ConnApiRefT *pConnApi, ConnApiConnInfoT *pInfo, ProtoMangleStatusE eStatus) { if (pInfo->bDemangling != TRUE) { // not demangling, nothing to report return; } ProtoMangleReport(pConnApi->pProtoMangle, eStatus, -1); pConnApi->bReporting = TRUE; pInfo->bDemangling = FALSE; } /*F********************************************************************************/ /*! \Function _ConnApiRemoveClient \Description Remove a current client from a game. \Input *pConnApi - pointer to module state \Input *pClient - pointer to client to remove \Input iClientIndex - index of client to remove \Version 04/08/2005 (jbrookes) */ /********************************************************************************F*/ static void _ConnApiRemoveClient(ConnApiRefT *pConnApi, ConnApiClientT *pClient, int32_t iClientIndex) { ConnApiConnStatusE eGameStatus = pClient->GameInfo.eStatus; ConnApiConnStatusE eVoipStatus = pClient->VoipInfo.eStatus; ConnApiConnStatusE eNewGameStatus; ConnApiConnStatusE eNewVoipStatus; // disconnect them _ConnApiDisconnectClient(pConnApi, pClient, iClientIndex, "removal"); eNewGameStatus = pClient->GameInfo.eStatus; eNewVoipStatus = pClient->VoipInfo.eStatus; // if we were demangling them, abort demangling #if CONNAPI_XBOX_NETWORKING if ((pClient->GameInfo.eStatus == CONNAPI_STATUS_MNGL) || (pClient->VoipInfo.eStatus == CONNAPI_STATUS_MNGL)) #else if ((pClient->GameInfo.bDemangling == TRUE) || (pClient->VoipInfo.bDemangling == TRUE)) #endif { NetPrintf(("connapi: aborting demangle of client 0x%08x - being removed from client list\n", pClient->ClientInfo.uId)); ProtoMangleControl(pConnApi->pProtoMangle, 'abrt', iClientIndex, 0, NULL); } // decrement overall count pConnApi->ClientList.iNumClients -= 1; ds_memclr(&pConnApi->ClientList.Clients[iClientIndex], sizeof(ConnApiClientT)); // send a disconnect event if (pConnApi->bRemoveCallback == TRUE) { if ((pConnApi->uConnFlags & CONNAPI_CONNFLAG_GAMECONN) != 0) { _ConnApiUpdateCallback(pConnApi, iClientIndex, CONNAPI_CBTYPE_GAMEEVENT, eGameStatus, eNewGameStatus); } if ((pConnApi->uConnFlags & CONNAPI_CONNFLAG_VOIPCONN) != 0) { _ConnApiUpdateCallback(pConnApi, iClientIndex, CONNAPI_CBTYPE_VOIPEVENT, eVoipStatus, eNewVoipStatus); } } } /*F********************************************************************************/ /*! \Function _ConnApiUpdateGameClient \Description Process game connection associated with this client. \Input *pConnApi - pointer to module state \Input *pClient - pointer to client to update \Input iClientIndex - index of client \Output int32_t - one=active, zero=disconnected \Version 01/06/2005 (jbrookes) */ /********************************************************************************F*/ static int32_t _ConnApiUpdateGameClient(ConnApiRefT *pConnApi, ConnApiClientT *pClient, int32_t iClientIndex) { ConnApiConnStatusE eStatus = pClient->GameInfo.eStatus; uint8_t bLocalAddr; int32_t iConnTimeout; // are game connections disabled? if ((pConnApi->uConnFlags & CONNAPI_CONNFLAG_GAMECONN) == 0) { // if we're not connected, just bail if ((pClient->GameInfo.eStatus == CONNAPI_STATUS_INIT) || (pClient->GameInfo.eStatus == CONNAPI_STATUS_DISC)) { return(0); } // if we're voip only and not already disconnected, kill the connection _ConnApiDestroyGameConnection(pConnApi, pClient, iClientIndex, "connection closed (voiponly)"); } // handle initial connection state if (pClient->GameInfo.eStatus == CONNAPI_STATUS_INIT) { ConnApiClientT *pClientUsed; // get address to connect with int32_t iConnectAddr = _ConnApiGetConnectAddr(pConnApi, pClient, &bLocalAddr, CONNAPI_CONNFLAG_GAMECONN, &pClientUsed); NetPrintf(("connapi: [%p] establishing game connection to client 0x%08x:%a at %d\n", pConnApi, pClient->ClientInfo.uId, iConnectAddr, NetTick())); // create tunnel? if ((pConnApi->bTunnelEnabled) && (pClientUsed->iTunnelId == 0)) { _ConnApiTunnelAlloc(pConnApi, pClientUsed, iClientIndex, iConnectAddr, bLocalAddr); } // do we have a tunnel to this client? if (pClientUsed->iTunnelId > 0) { NetPrintf(("connapi: [%p] tunnel allocated for client %d (local id 0x%08x, remote id 0x%08x); switching to use virtual address %a\n", pConnApi, iClientIndex, pClient->ClientInfo.uLocalClientId, (pClient->ClientInfo.bIsConnectivityHosted ? pClient->ClientInfo.uHostingServerId : pClient->ClientInfo.uRemoteClientId), pClientUsed->iTunnelId)); iConnectAddr = pClientUsed->iTunnelId; } // try to create game connection DirtyMemGroupEnter(pConnApi->iMemGroup, pConnApi->pMemGroupUserData); pClient->pGameUtilRef = NetGameUtilCreate(); DirtyMemGroupLeave(); if (pClient->pGameUtilRef != NULL) { char strConn[128], strConnName[64]; int32_t iConnFlags; // set game link options _ConnApiSetGamelinkOpt(pClient->pGameUtilRef, 'minp', pConnApi->iGameMinp); _ConnApiSetGamelinkOpt(pClient->pGameUtilRef, 'mout', pConnApi->iGameMout); _ConnApiSetGamelinkOpt(pClient->pGameUtilRef, 'mwid', pConnApi->iGameMwid); if (pConnApi->iGameUnackLimit != 0) { _ConnApiSetGamelinkOpt(pClient->pGameUtilRef, 'ulmt', pConnApi->iGameUnackLimit); } // set our client id (used by gameserver to uniquely identify us) NetGameUtilControl(pClient->pGameUtilRef, 'clid', pClient->ClientInfo.uLocalClientId); NetGameUtilControl(pClient->pGameUtilRef, 'rcid', pClient->ClientInfo.uRemoteClientId); // determine connection parameters iConnFlags = _ConnApiGetConnectParms(pConnApi, pClient, strConnName, sizeof(strConnName)); // format connect string ds_snzprintf(strConn, sizeof(strConn), "%a:%d:%d#%s", iConnectAddr, pClient->GameInfo.uLocalPort, pClient->GameInfo.uMnglPort, strConnName); // start the connection attempt NetGameUtilConnect(pClient->pGameUtilRef, iConnFlags, strConn, pConnApi->pCommConstruct); pClient->GameInfo.eStatus = CONNAPI_STATUS_CONN; // remember connection start time pClient->GameInfo.iConnStart = NetTick(); NetGameUtilControl(pClient->pGameUtilRef, 'meta', (pConnApi->bCommUdpMetadata || (pClient->ClientInfo.bIsConnectivityHosted)) ? 1 : 0); } else { NetPrintf(("connapi: unable to allocate util ref for connection %d to client 0x%08x\n", iClientIndex, pClient->ClientInfo.uId)); pClient->GameInfo.eStatus = CONNAPI_STATUS_DISC; _ConnApiClientReleaseProtoMangleResources(pConnApi, pClient); } } // waiting for connection if (pClient->GameInfo.eStatus == CONNAPI_STATUS_CONN) { void *pCommRef; if (pClient->pGameLinkRef == NULL) { // check for established connection if ((pCommRef = NetGameUtilComplete(pClient->pGameUtilRef)) != NULL) { DirtyMemGroupEnter(pConnApi->iMemGroup, pConnApi->pMemGroupUserData); pClient->pGameLinkRef = NetGameLinkCreate(pCommRef, FALSE, pConnApi->iLinkBufSize); DirtyMemGroupLeave(); if (pClient->pGameLinkRef != NULL) { NetPrintf(("connapi: game connection %d to client 0x%08x established at %d\n", iClientIndex, pClient->ClientInfo.uId, NetTick())); if (pClient->ClientInfo.bEnableQos) { NetPrintf(("connapi: enabling QoS over NetGameLink on connection %d to client 0x%08x\n", iClientIndex, pClient->ClientInfo.uId)); NetGameLinkControl(pClient->pGameLinkRef, 'sqos', pConnApi->iQosDuration, &pConnApi->iQosInterval); NetGameLinkControl(pClient->pGameLinkRef, 'lqos', pConnApi->iQosPacketSize, NULL); } // if we were demangling, report success _ConnApiDemangleReport(pConnApi, &pClient->GameInfo, PROTOMANGLE_STATUS_CONNECTED); // save socket ref for multi-demangle if we need it if (!pConnApi->bTunnelEnabled) { NetGameUtilStatus(pClient->pGameUtilRef, 'sock', &pConnApi->uGameSockRef, sizeof(pConnApi->uGameSockRef)); } // indicate we've connected pClient->GameInfo.uConnFlags |= CONNAPI_CONNFLAG_CONNECTED; } else { NetPrintf(("connapi: unable to allocate link ref for connection %d to client 0x%08x\n", iClientIndex, pClient->ClientInfo.uId)); pClient->GameInfo.eStatus = CONNAPI_STATUS_DISC; _ConnApiClientReleaseProtoMangleResources(pConnApi, pClient); } } } // check for gamelink saying we're connected if (pClient->pGameLinkRef != NULL) { NetGameLinkStatT Stat; // give time to NetGameLink to run any connection-related processes NetGameLinkUpdate(pClient->pGameLinkRef); // get link stats NetGameLinkStatus(pClient->pGameLinkRef, 'stat', 0, &Stat, sizeof(NetGameLinkStatT)); // see if we're open if (Stat.isopen == TRUE) { // mark as active NetPrintf(("connapi: game connection %d to client 0x%08x is active at %d\n", iClientIndex, pClient->ClientInfo.uId, NetTick())); pClient->GameInfo.eStatus = CONNAPI_STATUS_ACTV; } } // check for connection timeout iConnTimeout = pConnApi->iConnTimeout; // check for connection timeout // The connection timeout, and a subsequent disconnection, should only occur if we still have not connected to the peer. // If we have a pGameLinkRef, then that means we MUST have established a connection to the peer, but are still doing QoS. if ((pClient->GameInfo.eStatus == CONNAPI_STATUS_CONN) && (pClient->pGameLinkRef == NULL) && (NetTickDiff(NetTick(), pClient->GameInfo.iConnStart) > iConnTimeout)) { _ConnApiDestroyGameConnection(pConnApi, pClient, iClientIndex, "connection timeout"); // on xboxone, we never want to go back to MNGL state from CONN state #if CONNAPI_XBOX_NETWORKING NetPrintf(("connapi: game connection to client 0x%08x failed\n", pClient->ClientInfo.uId)); if (pClient->GameInfo.bDemangling) { _ConnApiDemangleReport(pConnApi, &pClient->GameInfo, PROTOMANGLE_STATUS_FAILED); } pClient->GameInfo.eStatus = CONNAPI_STATUS_DISC; #else // initial attempt to connect failed if (pClient->GameInfo.bDemangling == FALSE) { if ((pConnApi->bDemanglerEnabled) && (pClient->ClientInfo.bIsConnectivityHosted == FALSE) && (pConnApi->eGameTopology != CONNAPI_GAMETOPOLOGY_SERVERHOSTED)) { NetPrintf(("connapi: game status=mngl for connection %d to client 0x%08x\n", iClientIndex, pClient->ClientInfo.uId)); pClient->GameInfo.eStatus = CONNAPI_STATUS_MNGL; } else { if (pConnApi->bDemanglerEnabled && pClient->ClientInfo.bIsConnectivityHosted == TRUE) { NetPrintf(("connapi: demangling skipped for connection %d to client 0x%08x because the client is connectivity hosted\n", iClientIndex, pClient->ClientInfo.uId)); } pClient->GameInfo.eStatus = CONNAPI_STATUS_DISC; } } else { NetPrintf(("connapi: game connection to client 0x%08x after demangle failed\n", pClient->ClientInfo.uId)); _ConnApiDemangleReport(pConnApi, &pClient->GameInfo, PROTOMANGLE_STATUS_FAILED); pClient->GameInfo.eStatus = CONNAPI_STATUS_DISC; } #endif } // timeout // set the packet receive flag if a packet was received if ((pClient->pGameUtilRef != NULL) && (NetGameUtilStatus(pClient->pGameUtilRef, 'pkrc', NULL, 0) == TRUE)) { pClient->GameInfo.uConnFlags |= CONNAPI_CONNFLAG_PKTRECEIVED; } } // waiting for connection // update connection status during active phase if (pClient->GameInfo.eStatus == CONNAPI_STATUS_ACTV) { // get link stats NetGameLinkStatT Stat; NetGameLinkStatus(pClient->pGameLinkRef, 'stat', 0, &Stat, sizeof(NetGameLinkStatT)); // make sure connection is still open if (Stat.isopen == FALSE) { _ConnApiDestroyGameConnection(pConnApi, pClient, iClientIndex, "connection closed"); } // see if we've timed out else if (NetTickDiff(Stat.tick, Stat.rcvdlast) > pConnApi->iTimeout) { _ConnApiDestroyGameConnection(pConnApi, pClient, iClientIndex, "connection timed out"); } } // trigger callback if state change _ConnApiUpdateCallback(pConnApi, iClientIndex, CONNAPI_CBTYPE_GAMEEVENT, eStatus, (ConnApiConnStatusE)pClient->GameInfo.eStatus); // return active or inactive return(pClient->GameInfo.eStatus != CONNAPI_STATUS_DISC); } /*F********************************************************************************/ /*! \Function _ConnApiUpdateVoipClient \Description Process voip connection associated with this connection. \Input *pConnApi - pointer to module state \Input *pClient - pointer to connection to update \Input iClientIndex - index of connection \Output int32_t - one=active, zero=disconnected \Version 01/06/2005 (jbrookes) */ /********************************************************************************F*/ static int32_t _ConnApiUpdateVoipClient(ConnApiRefT *pConnApi, ConnApiClientT *pClient, int32_t iClientIndex) { ConnApiConnStatusE eStatus = pClient->VoipInfo.eStatus; int32_t iVoipConnId, iVoipStatus = 0; uint8_t bLocalAddr; // are voip connections disabled? if ((pConnApi->uConnFlags & CONNAPI_CONNFLAG_VOIPCONN) == 0) { // if we're not connected, just bail if ((pClient->VoipInfo.eStatus == CONNAPI_STATUS_INIT) || (pClient->VoipInfo.eStatus == CONNAPI_STATUS_DISC)) { return(0); } // if we're game only and not already disconnected, close the connection _ConnApiDestroyVoipConnection(pConnApi, pClient, "connection closed (gameonly)"); } // handle initial connection state if (pClient->VoipInfo.eStatus == CONNAPI_STATUS_INIT) { ConnApiClientT *pClientUsed; int32_t iAdjustedVoipClientIndex = (pConnApi->eVoipTopology == CONNAPI_VOIPTOPOLOGY_SERVERHOSTED) ? iClientIndex - 1 : iClientIndex; // get address to connect with int32_t iConnectAddr = _ConnApiGetConnectAddr(pConnApi, pClient, &bLocalAddr, CONNAPI_CONNFLAG_VOIPCONN, &pClientUsed); NetPrintf(("connapi: [%p] establishing voip connection to client 0x%08x:%a at %d\n", pConnApi, pClient->ClientInfo.uId, iConnectAddr, NetTick())); // create tunnel? if ((pConnApi->bTunnelEnabled) && (pClientUsed->iTunnelId == 0)) { _ConnApiVoipTunnelAlloc(pConnApi, pClientUsed, iClientIndex, iConnectAddr, bLocalAddr); } // do we have a tunnel to this client? if (pClientUsed->iTunnelId > 0) { NetPrintf(("connapi: [%p] tunnel allocated for client %d; switching to use virtual address %a\n", pConnApi, iClientIndex, pClientUsed->iTunnelId)); iConnectAddr = pClientUsed->iTunnelId; } // initiate connection attempt // $$todo - deprecate 'vcid' and add a new uLocalClientId parameter to VoipGroupConnect() - the current implementation can be problematic if // the return conn id does not correspond to the one set with 'vcid' VoipGroupControl(pConnApi->pVoipGroupRef, 'vcid', iAdjustedVoipClientIndex, 0, &pClient->ClientInfo.uLocalClientId); iVoipConnId = VoipGroupConnect(pConnApi->pVoipGroupRef, iAdjustedVoipClientIndex, iConnectAddr, pClient->VoipInfo.uMnglPort, pClient->VoipInfo.uLocalPort, pClient->ClientInfo.uId, pConnApi->iSessId, pClient->ClientInfo.bIsConnectivityHosted, pClient->ClientInfo.uRemoteClientId); if (iVoipConnId >= 0) { pClient->iVoipConnId = iVoipConnId; pClient->VoipInfo.eStatus = CONNAPI_STATUS_CONN; pClient->VoipInfo.iConnStart = NetTick(); } else { NetPrintf(("connapi: unable to init voip for client index %d (client id: 0x%08x)\n", iClientIndex, pClient->ClientInfo.uId)); pClient->VoipInfo.eStatus = CONNAPI_STATUS_DISC; _ConnApiClientReleaseProtoMangleResources(pConnApi, pClient); } } // get connection status if ((pClient->VoipInfo.eStatus == CONNAPI_STATUS_CONN) || (pClient->VoipInfo.eStatus == CONNAPI_STATUS_ACTV)) { iVoipStatus = VoipGroupConnStatus(pConnApi->pVoipGroupRef, pClient->iVoipConnId); } // for both cases below, CONNAPI_STATUS_CONN and CONNAPI_STATUS_ACTV, when we detect a disconnection, // we won't set the iVoipConnId to NONE nor trigger a disconnection. This is because the voipgroup code, // in order to provide correct voip connection sharing, needs to be told only of authoritative "connect" // and "disconnect" by the higher-level lobby (plasma, lobbysdk, blazesdk, ...) techonology. // update connection status during connect phase if (pClient->VoipInfo.eStatus == CONNAPI_STATUS_CONN) { // check for established connection if (iVoipStatus & VOIP_CONN_CONNECTED) { NetPrintf(("connapi: voip connection for client index %d (client id: 0x%08x) established at %d\n", iClientIndex, pClient->ClientInfo.uId, NetTick())); // if we were demangling, report success _ConnApiDemangleReport(pConnApi, &pClient->VoipInfo, PROTOMANGLE_STATUS_CONNECTED); // save socket ref for multi-demangle if we need it if (!pConnApi->bTunnelEnabled) { VoipGroupStatus(pConnApi->pVoipGroupRef, 'sock', 0, &pConnApi->uVoipSockRef, sizeof(pConnApi->uVoipSockRef)); } // mark as active pClient->VoipInfo.eStatus = CONNAPI_STATUS_ACTV; pClient->VoipInfo.uConnFlags |= CONNAPI_CONNFLAG_CONNECTED; } else if (iVoipStatus & VOIP_CONN_STOPPED) { NetPrintf(("connapi: voip connection attempt to client 0x%08x failed at %d\n", pClient->ClientInfo.uId, NetTick())); // on xboxone, we never want to go back to MNGL state from CONN state #if CONNAPI_XBOX_NETWORKING NetPrintf(("connapi: voip connection attempt to client 0x%08x failed\n", pClient->ClientInfo.uId)); if (pClient->VoipInfo.bDemangling == TRUE) { _ConnApiDemangleReport(pConnApi, &pClient->VoipInfo, PROTOMANGLE_STATUS_FAILED); } pClient->VoipInfo.eStatus = CONNAPI_STATUS_DISC; #else if (pClient->VoipInfo.bDemangling == TRUE) { NetPrintf(("connapi: voip connection attempt to client 0x%08x after demangle failed\n", pClient->ClientInfo.uId)); _ConnApiDemangleReport(pConnApi, &pClient->VoipInfo, PROTOMANGLE_STATUS_FAILED); pClient->VoipInfo.eStatus = CONNAPI_STATUS_DISC; } else { if (pConnApi->bDemanglerEnabled && pClient->ClientInfo.bIsConnectivityHosted == FALSE) { NetPrintf(("connapi: voip status=mngl for client index %d (client id: 0x%08x)\n", iClientIndex, pClient->ClientInfo.uId)); pClient->VoipInfo.eStatus = CONNAPI_STATUS_MNGL; } else { if (pConnApi->bDemanglerEnabled && pClient->ClientInfo.bIsConnectivityHosted == TRUE) { NetPrintf(("connapi: voip connection demangling skipped for client index %d (client id: 0x%08x) because the client is connectivity hosted\n", iClientIndex, pClient->ClientInfo.uId)); } pClient->VoipInfo.eStatus = CONNAPI_STATUS_DISC; } } // bDemangling = FALSE #endif } // REMOTE_DISCONNECTED } // STATUS_CONN // update client in active state if (pClient->VoipInfo.eStatus == CONNAPI_STATUS_ACTV) { if (iVoipStatus & VOIP_CONN_STOPPED) { NetPrintf(("connapi: voip connection to client 0x%08x terminated at %d\n", pClient->ClientInfo.uId, NetTick())); pClient->VoipInfo.eStatus = CONNAPI_STATUS_DISC; _ConnApiClientReleaseProtoMangleResources(pConnApi, pClient); } } // trigger callback if state change _ConnApiUpdateCallback(pConnApi, iClientIndex, CONNAPI_CBTYPE_VOIPEVENT, eStatus, (ConnApiConnStatusE)pClient->VoipInfo.eStatus); // return active or inactive return(pClient->VoipInfo.eStatus != CONNAPI_STATUS_DISC); } /*F********************************************************************************/ /*! \Function _ConnApiUpdateDemangleReport \Description Update demangler during the Report phase. \Input *pConnApi - pointer to module state \Output uint32_t - TRUE if reporting is in progress, FALSE otherwise \Version 01/17/2005 (jbrookes) */ /********************************************************************************F*/ static uint32_t _ConnApiUpdateDemangleReport(ConnApiRefT *pConnApi) { // if not reporting, don't process if (pConnApi->bReporting != FALSE) { #if CONNAPI_XBOX_NETWORKING // there is no reporting phase on xbox one... so we alway fake that reporting is complete pConnApi->bReporting = FALSE; #else // update client ProtoMangleUpdate(pConnApi->pProtoMangle); // check for completion if (ProtoMangleComplete(pConnApi->pProtoMangle, NULL, NULL) != 0) { pConnApi->bReporting = FALSE; } #endif } return(pConnApi->bReporting); } /*F********************************************************************************/ /*! \Function _ConnApiUpdateDemangle \Description Update client connection in CONNAPI_STATUS_MNGL state. \Input *pConnApi - pointer to module state \Input *pClient - pointer to client to update \Input iClientIndex - index of client \Input *pConnInfo - pointer to connection info (game or voip) \Input iType - type of connection (zero=game, one=voip) \Version 01/13/2005 (jbrookes) */ /********************************************************************************F*/ static void _ConnApiUpdateDemangle(ConnApiRefT *pConnApi, ConnApiClientT *pClient, int32_t iClientIndex, ConnApiConnInfoT *pConnInfo, int32_t iType) { static const char _Types[2][5] = { "game", "voip" }; static uint32_t _Flags[2] = { CONNAPI_CONNFLAG_GAMECONN, CONNAPI_CONNFLAG_VOIPCONN }; ConnApiCbTypeE eCbType; #if !CONNAPI_XBOX_NETWORKING int32_t iClient; #endif // initialize eType if (iType == 0) { eCbType = CONNAPI_CBTYPE_GAMEEVENT; } else { eCbType = CONNAPI_CBTYPE_VOIPEVENT; } // ignore game/voip demangle if we're not doing game/voip connections if ((_Flags[iType] & pConnApi->uConnFlags) == 0) { return; } #if !CONNAPI_XBOX_NETWORKING // if anyone is in a connecting state, wait to demangle for (iClient = 0; iClient < pConnApi->ClientList.iMaxClients; iClient++) { if ((pConnApi->ClientList.Clients[iClient].GameInfo.eStatus == CONNAPI_STATUS_CONN) || (pConnApi->ClientList.Clients[iClient].VoipInfo.eStatus == CONNAPI_STATUS_CONN)) { NetPrintf(("connapi: [%p] deferring demangle until there are no ongoing connection attempts\n", pConnApi)); return; } } #endif // tunnel-specific processing if (pConnApi->bTunnelEnabled) { // if we've already demangled the tunnel port, use previous demangle result if (pClient->uFlags & CONNAPI_CLIENTFLAG_TUNNELPORTDEMANGLED) { ConnApiConnStatusE eStatus = pConnInfo->eStatus; pConnInfo->eStatus = CONNAPI_STATUS_INIT; pConnInfo->uConnFlags |= CONNAPI_CONNFLAG_DEMANGLED; NetPrintf(("connapi: [%p] reusing previously demangled tunnel port\n", pConnApi)); // trigger callback if state change _ConnApiUpdateCallback(pConnApi, iClientIndex, eCbType, eStatus, (ConnApiConnStatusE)pConnInfo->eStatus); return; } } else { // if we've already resolved the secure address, use it if (pClient->uFlags & CONNAPI_CLIENTFLAG_SECUREADDRRESOLVED) { ConnApiConnStatusE eStatus = pConnInfo->eStatus; pConnInfo->eStatus = CONNAPI_STATUS_INIT; pConnInfo->uConnFlags |= CONNAPI_CONNFLAG_DEMANGLED; NetPrintf(("connapi: [%p] reusing previously resolved secure address\n", pConnApi)); // trigger callback if state change _ConnApiUpdateCallback(pConnApi, iClientIndex, eCbType, eStatus, (ConnApiConnStatusE)pConnInfo->eStatus); return; } } // are we in an idle state? if (ProtoMangleStatus(pConnApi->pProtoMangle, 'idle', NULL, iClientIndex)) { char strSess[64]; intptr_t uSockRef; uint32_t uDemanglePort = 0; // show we're demangling pConnInfo->bDemangling = TRUE; // create session id _ConnApiGenerateSessionKey(pConnApi, pClient, iClientIndex, strSess, sizeof(strSess), _Types[iType]); // get socket ref if (pConnApi->bTunnelEnabled) { uSockRef = pConnApi->uTunlSockRef; } else if (iType == 0) { uSockRef = pConnApi->uGameSockRef; } else { uSockRef = pConnApi->uVoipSockRef; } // if socket ref is null, connect normally if (uSockRef == 0) { #if !CONNAPI_RNDLCLDEMANGLEPORT uDemanglePort = (iType == 0) ? pConnApi->uGamePort : pConnApi->uVoipPort; uDemanglePort = (pConnApi->bTunnelEnabled) ? (unsigned)pConnApi->iTunnelPort : uDemanglePort; #else uDemanglePort = _ConnApiGenerateDemanglePort(pConnApi); if (pConnApi->bTunnelEnabled) { // if we're tunneling, we need to recreate the tunnel socket bound to the new port ProtoTunnelControl(pConnApi->pProtoTunnel, 'bind', uDemanglePort, 0, NULL); } else { // if not tunneling, we need to update the local port info with the new local (bind) demangle port pConnInfo->uLocalPort = (uint16_t)uDemanglePort; } #endif // if tunneling we always want to use the tunnel socket ref for demangling if (pConnApi->bTunnelEnabled) { ProtoTunnelStatus(pConnApi->pProtoTunnel, 'sock', 0, &pConnApi->uTunlSockRef, sizeof(pConnApi->uTunlSockRef)); uSockRef = pConnApi->uTunlSockRef; } } if (iType == 0) { pClient->GameInfo.iConnStart = NetTick(); } else { pClient->VoipInfo.iConnStart = NetTick(); } #if CONNAPI_XBOX_NETWORKING { // since the SecureDeviceAddr fits in the DirtyAddrT, it is safe to use a buffer as large as a DirtyAddrT char aSecureDeviceAddressBlob[DIRTYADDR_MACHINEADDR_MAXLEN]; int32_t iBlobSize; if (DirtyAddrGetInfoXboxOne(&pConnApi->ClientList.Clients[iClientIndex].ClientInfo.DirtyAddr, NULL, aSecureDeviceAddressBlob, &iBlobSize)) { NetPrintf(("connapi: initiating %s secure address resolution of client 0x%08x at %d\n", _Types[iType], pClient->ClientInfo.uId, NetTick())); ProtoMangleConnect2(pConnApi->pProtoMangle, iClientIndex, aSecureDeviceAddressBlob, iBlobSize); } else { ConnApiConnStatusE eStatus = pConnInfo->eStatus; NetPrintf(("connapi: failed to initiate the %s secure address resolution of client 0x%08x due to device address being invalid\n", _Types[iType], pClient->ClientInfo.uId)); pConnInfo->eStatus = CONNAPI_STATUS_DISC; _ConnApiUpdateCallback(pConnApi, iClientIndex, eCbType, eStatus, (ConnApiConnStatusE)pConnInfo->eStatus); return; } } #else // before demangling, flush the tunnel to make sure there are no buffered packets if (pConnApi->bTunnelEnabled) { ProtoTunnelControl(pConnApi->pProtoTunnel, 'flsh', pClient->iTunnelId, 0, NULL); } // kick off demangling process if (uSockRef == 0) { NetPrintf(("connapi: initiating %s:%d demangle of client 0x%08x at %d\n", _Types[iType], uDemanglePort, pClient->ClientInfo.uId, NetTick())); ProtoMangleConnect(pConnApi->pProtoMangle, uDemanglePort, strSess); } else { NetPrintf(("connapi: initiating %s demangle of client 0x%08x using sockref 0x%08x at %d\n", _Types[iType], pClient->ClientInfo.uId, uSockRef, NetTick())); ProtoMangleConnectSocket(pConnApi->pProtoMangle, uSockRef, strSess); } #endif } else { if (pConnInfo->bDemangling != FALSE) { int32_t iAddr, iPort, iResult; // update client ProtoMangleUpdate(pConnApi->pProtoMangle); // check for completion #if CONNAPI_XBOX_NETWORKING iPort = iClientIndex; #endif if ((iResult = ProtoMangleComplete(pConnApi->pProtoMangle, &iAddr, &iPort)) != 0) { ConnApiConnStatusE eStatus = pConnInfo->eStatus; if (eStatus != CONNAPI_STATUS_ACTV) { if (iResult > 0) { #if CONNAPI_XBOX_NETWORKING NetPrintf(("connapi: %s secure address resolution for user 0x%08x is successful ipaddr=%a at %d\n", _Types[iType], pClient->ClientInfo.uId, iAddr, NetTick())); iPort = pConnInfo->uMnglPort; #else NetPrintf(("connapi: %s demangle of client 0x%08x successful port=%d at %d\n", _Types[iType], pClient->ClientInfo.uId, iPort, NetTick())); #endif pClient->ClientInfo.uAddr = pClient->ClientInfo.uLocalAddr = iAddr; if (pConnApi->bTunnelEnabled) { // for xboxone, clients do not yet have a valid tunnel id at this point because client enters to MNGL state before INIT state (unlike other platforms) #if !CONNAPI_XBOX_NETWORKING ProtoTunnelControl(pConnApi->pProtoTunnel, 'rprt', pClient->iTunnelId, iPort, NULL); #endif pClient->uFlags |= CONNAPI_CLIENTFLAG_TUNNELPORTDEMANGLED; } else { pConnInfo->uMnglPort = (uint16_t)iPort; #if CONNAPI_XBOX_NETWORKING pClient->uFlags |= CONNAPI_CLIENTFLAG_SECUREADDRRESOLVED; #endif } pConnInfo->eStatus = CONNAPI_STATUS_INIT; pConnInfo->uConnFlags |= CONNAPI_CONNFLAG_DEMANGLED; } else { NetPrintf(("connapi: %s demangle of client 0x%08x failed (timeout=%s)\n", _Types[iType], pClient->ClientInfo.uId, ProtoMangleStatus(pConnApi->pProtoMangle, 'time', NULL, iClientIndex) ? "true" : "false")); pConnInfo->eStatus = CONNAPI_STATUS_DISC; } } else { NetPrintf(("connapi: [%p] %s demangle of client 0x%08x finished with %d but ignored because connection aleady active (timeout=%s)\n", pConnApi, _Types[iType], pClient->ClientInfo.uId, iResult, ProtoMangleStatus(pConnApi->pProtoMangle, 'time', NULL, iClientIndex) ? "true" : "false")); } // trigger callback if state change _ConnApiUpdateCallback(pConnApi, iClientIndex, eCbType, eStatus, (ConnApiConnStatusE)pConnInfo->eStatus); } } } } /*F********************************************************************************/ /*! \Function _ConnApiUpdateRemoval \Description Scan through client list and remove clients marked for removal. \Input *pConnApi - pointer to module state \Version 04/11/2005 (jbrookes) */ /********************************************************************************F*/ static void _ConnApiUpdateRemoval(ConnApiRefT *pConnApi) { ConnApiClientT *pClient; int32_t iClientIndex; for (iClientIndex = 0; iClientIndex < pConnApi->ClientList.iMaxClients; iClientIndex++) { // ref client pClient = &pConnApi->ClientList.Clients[iClientIndex]; // if client needs to be removed, remove them if (pClient->uFlags & CONNAPI_CLIENTFLAG_REMOVE) { _ConnApiRemoveClient(pConnApi, pClient, iClientIndex); } } } /*F********************************************************************************/ /*! \Function _ConnApiRemoveClientSetup \Description Set up a client for removal from the game. \Input *pConnApi - pointer to module state \Input iClientIndex - index of client to remove (used if pClientName is NULL) \Input uFlags - client removal flags \Notes If this function is called inside of a ConnApi callback, the removal will be deferred until the next time NetConnIdle() is called. Otherwise, the removal will happen immediately. \Version 04/08/2005 (jbrookes) */ /********************************************************************************F*/ static void _ConnApiRemoveClientSetup(ConnApiRefT *pConnApi, int32_t iClientIndex, uint16_t uFlags) { ConnApiClientT *pClient; // don't allow self removal if (iClientIndex == pConnApi->iSelf) { NetPrintf(("connapi: [%p] can't remove self from game\n", pConnApi)); return; } // ref the client and mark them for removal pClient = &pConnApi->ClientList.Clients[iClientIndex]; pClient->uFlags |= uFlags; // if we're not in a callback, do the removal immediately if (pConnApi->bInCallback == FALSE) { _ConnApiUpdateRemoval(pConnApi); } } /*F********************************************************************************/ /*! \Function _ConnApiUpdateConnections \Description Update ConnApi connections. \Input *pConnApi - pointer to module state \Output int32_t - number of connections that are not in the DISC state \Version 01/06/2005 (jbrookes) */ /********************************************************************************F*/ static int32_t _ConnApiUpdateConnections(ConnApiRefT *pConnApi) { ConnApiClientT *pClient; int32_t iActive, iClientIndex; uint32_t bDemangling; // update game connections for (iActive = 0, iClientIndex = 0; iClientIndex < pConnApi->ClientList.iMaxClients; iClientIndex++) { // ref connection pClient = &pConnApi->ClientList.Clients[iClientIndex]; // don't update if iClientIndex is us or unallocated if (!pClient->bAllocated) { continue; } if (pClient->uConnFlags & CONNAPI_CONNFLAG_GAMECONN) { // process game connection iActive += _ConnApiUpdateGameClient(pConnApi, pClient, iClientIndex); } } // update voip connections if (pConnApi->pVoipRef != NULL) { for (iClientIndex = 0; iClientIndex < pConnApi->ClientList.iMaxClients; iClientIndex++) { // ref connection pClient = &pConnApi->ClientList.Clients[iClientIndex]; // don't update if iClientIndex is us or unallocated if (!pClient->bAllocated) { continue; } if (pClient->uConnFlags & CONNAPI_CONNFLAG_VOIPCONN) { // process voip connection iActive += _ConnApiUpdateVoipClient(pConnApi, pClient, iClientIndex); } } } // update reporting bDemangling = _ConnApiUpdateDemangleReport(pConnApi); // update game demangling for (iClientIndex = 0; iClientIndex < pConnApi->ClientList.iMaxClients; iClientIndex++) { // ref connection pClient = &pConnApi->ClientList.Clients[iClientIndex]; // don't update if iClientIndex is us or unallocated if ((iClientIndex == pConnApi->iSelf) || !pClient->bAllocated) { continue; } // demangle game connection? #if CONNAPI_XBOX_NETWORKING // on xboxone, we don't serialize demangling of different clients, we want them occurring in parallel if ((pClient->GameInfo.eStatus == CONNAPI_STATUS_MNGL) && (pClient->uConnFlags & CONNAPI_CONNFLAG_GAMECONN)) #else if ((pClient->GameInfo.eStatus == CONNAPI_STATUS_MNGL) && (pClient->uConnFlags & CONNAPI_CONNFLAG_GAMECONN) && (bDemangling == FALSE)) #endif { _ConnApiUpdateDemangle(pConnApi, pClient, iClientIndex, &pClient->GameInfo, 0); } bDemangling |= pClient->GameInfo.bDemangling; } // update voip demangling for (iClientIndex = 0; iClientIndex < pConnApi->ClientList.iMaxClients; iClientIndex++) { // ref connection pClient = &pConnApi->ClientList.Clients[iClientIndex]; // don't update if iClientIndex is us or unallocated if ((iClientIndex == pConnApi->iSelf) || !pClient->bAllocated) { continue; } // demangle voip connection? #if CONNAPI_XBOX_NETWORKING // on xboxone, we don't serialize demangling of different clients, we want them occurring in parallel if ((pClient->VoipInfo.eStatus == CONNAPI_STATUS_MNGL)) #else if ((pClient->VoipInfo.eStatus == CONNAPI_STATUS_MNGL) && (bDemangling == FALSE)) #endif { _ConnApiUpdateDemangle(pConnApi, pClient, iClientIndex, &pClient->VoipInfo, 1); } } // update tunnel if ((pConnApi->bTunnelEnabled != 0) && (pConnApi->pProtoTunnel != NULL)) { ProtoTunnelUpdate(pConnApi->pProtoTunnel); } return(iActive); } /*F********************************************************************************/ /*! \Function _ConnApiIdle \Description NetConn idle function to update the ConnApi module. \Input *pData - pointer to module state \Input uTick - current tick count \Notes This function is installed as a NetConn Idle function. NetConnIdle() must be regularly polled for this function to be called. \Version 01/06/2005 (jbrookes) */ /********************************************************************************F*/ static void _ConnApiIdle(void *pData, uint32_t uTick) { ConnApiRefT *pConnApi = (ConnApiRefT *)pData; if (pConnApi->bAutoUpdate == TRUE) { ConnApiUpdate(pConnApi); } } /*** Public functions *************************************************************/ /*F********************************************************************************/ /*! \Function ConnApiCreate2 \Description Create the module state. \Input iGamePort - game connection port \Input iMaxClients - maximum number of clients allowed \Input *pCallback - pointer to user callback \Input *pUserData - pointer to user data \Input *pConstruct - comm construct function \Output ConnApiRefT * - pointer to module state, or NULL \Version 01/04/2005 (jbrookes) */ /********************************************************************************F*/ ConnApiRefT *ConnApiCreate2(int32_t iGamePort, int32_t iMaxClients, ConnApiCallbackT *pCallback, void *pUserData, CommAllConstructT *pConstruct) { ConnApiRefT *pConnApi; int32_t iMemGroup; void *pMemGroupUserData; int32_t iSize; // Query current mem group data DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); // calculate size of module state iSize = sizeof(*pConnApi) + (sizeof(ConnApiClientT) * (iMaxClients - 1)); // allocate and init module state if ((pConnApi = DirtyMemAlloc(iSize, CONNAPI_MEMID, iMemGroup, pMemGroupUserData)) == NULL) { NetPrintf(("connapi: could not allocate module state... connapi initialization aborted!\n")); return(NULL); } ds_memclr(pConnApi, iSize); pConnApi->iMemGroup = iMemGroup; pConnApi->pMemGroupUserData = pMemGroupUserData; if ((pConnApi->pVoipGroupRef = VoipGroupCreate(_ConnApi_iMaxVoipGroups)) == NULL) { // release module memory DirtyMemFree(pConnApi, CONNAPI_MEMID, pConnApi->iMemGroup, pConnApi->pMemGroupUserData); NetPrintf(("connapi: [%p] no more voip groups available... connapi initialization aborted!\n", pConnApi)); return(NULL); } // register connection sharing callback with underlying voip group instance VoipGroupSetConnSharingEventCallback(pConnApi->pVoipGroupRef, _ConnApiVoipGroupConnSharingCallback, pConnApi); // save info pConnApi->uGamePort = (uint16_t)iGamePort; pConnApi->uVoipPort = VOIP_PORT; pConnApi->ClientList.iMaxClients = iMaxClients; pConnApi->pCallback[0] = (pCallback != NULL) ? pCallback : _ConnApiDefaultCallback; pConnApi->pUserData[0] = pUserData; pConnApi->pCommConstruct = pConstruct; // set default values pConnApi->uConnFlags = CONNAPI_CONNFLAG_GAMEVOIP; pConnApi->iLinkBufSize = CONNAPI_LINKBUFDEFAULT; pConnApi->iConnTimeout = CONNAPI_CONNTIMEOUT_DEFAULT; pConnApi->iTimeout = CONNAPI_TIMEOUT_DEFAULT; pConnApi->iConfigMnglTimeout = CONNAPI_DEMANGLER_TIMEOUT; pConnApi->iConfigMnglTimeoutFailover = CONNAPI_DEMANGLER_WITH_FAILOVER_TIMEOUT; pConnApi->iCurrentMnglTimeout = 0; pConnApi->iTunnelPort = 3658; pConnApi->bDemanglerEnabled = TRUE; pConnApi->bAutoUpdate = TRUE; #if !CONNAPI_XBOX_NETWORKING pConnApi->bDoAdvertising = TRUE; #endif pConnApi->uNetMask = 0xffffffff; pConnApi->iQosDuration = 0; // QoS is disabled by default pConnApi->iQosInterval = 0; pConnApi->iQosPacketSize = 0; pConnApi->iGameHostIndex = -1; pConnApi->iVoipHostIndex = -1; // set default demangler server and create demangler ds_strnzcpy(pConnApi->strDemanglerServer, PROTOMANGLE_SERVER, sizeof(pConnApi->strDemanglerServer)); // add update function to netconn idle handler NetConnIdleAdd(_ConnApiIdle, pConnApi); // return module state to caller return(pConnApi); } /*F********************************************************************************/ /*! \Function ConnApiOnline \Description This function should be called once the user has logged on and the input parameters are available \Input *pConnApi - pointer to module state \Input *pGameName - pointer to game resource string (eg cso/NCAA-2006/na) \Input uSelfId - unique identifier for the local connapi client \Input eGameTopology- type of game \Input eVoipTopology- type of voip \Version 01/06/2005 (jbrookes) */ /********************************************************************************F*/ void ConnApiOnline(ConnApiRefT *pConnApi, const char *pGameName, uint32_t uSelfId, ConnApiGameTopologyE eGameTopology, ConnApiVoipTopologyE eVoipTopology) { char strAdvt[32]; NetPrintf(("connapi: [%p] ConnApiOnline() invoked with uSelfId=0x%08x and pGameName=%s\n", pConnApi, uSelfId, pGameName)); // save info ds_strnzcpy(pConnApi->strGameName, pGameName, sizeof(pConnApi->strGameName)); pConnApi->uSelfId = uSelfId; // if voip was disabled before online was called change the topology to disabled if ((pConnApi->uConnFlags & CONNAPI_CONNFLAG_VOIPCONN) == 0) { NetPrintf(("connapi: [%p] requested voip topology ignored because voip globally disabled\n", pConnApi)); eVoipTopology = CONNAPI_VOIPTOPOLOGY_DISABLED; } // save the topology and set the connection flags accordingly if ((pConnApi->eGameTopology = eGameTopology) == CONNAPI_GAMETOPOLOGY_DISABLED) { pConnApi->uConnFlags &= ~CONNAPI_CONNFLAG_GAMECONN; } if ((pConnApi->eVoipTopology = eVoipTopology) == CONNAPI_VOIPTOPOLOGY_DISABLED) { pConnApi->uConnFlags &= ~CONNAPI_CONNFLAG_VOIPCONN; } // get VoIP ref if ((pConnApi->eVoipTopology != CONNAPI_VOIPTOPOLOGY_DISABLED) && (pConnApi->pVoipRef == NULL)) { if ((pConnApi->pVoipRef = VoipGetRef()) == NULL) { NetPrintf(("connapi: [%p] critical error! ConnApiOnline() is invoked on a voip-enabled ConnApi before VoipStartup() was called externally.\n", pConnApi)); return; } } // set memory grouping, this requires DirtyMemGroupLeave() to be called before return DirtyMemGroupEnter(pConnApi->iMemGroup, pConnApi->pMemGroupUserData); // create util ref for subnet advertising if (pConnApi->bDoAdvertising) { NetPrintf(("connapi: [%p] creating NetGameUtil ref used for advertising purposes\n", pConnApi)); if (pConnApi->pGameUtilRef == NULL) { if ((pConnApi->pGameUtilRef = NetGameUtilCreate()) == NULL) { NetPrintf(("connapi: [%p] failed to create the NetGameUtil ref used for advertising purposes\n", pConnApi)); DirtyMemGroupLeave(); return; } } else { NetPrintf(("connapi: [%p] can't create the NetGameUtil ref used for advertising purposes because there already exists one\n", pConnApi)); DirtyMemGroupLeave(); return; } } else { NetPrintf(("connapi: [%p] skipped creation of the NetGameUtil ref used for advertising purposes\n", pConnApi)); } // on non-XboxOne platforms handle the cases where we should disable the demangler #if !CONNAPI_XBOX_NETWORKING if ((pConnApi->eGameTopology == CONNAPI_GAMETOPOLOGY_SERVERHOSTED) && (pConnApi->eVoipTopology != CONNAPI_VOIPTOPOLOGY_PEERWEB)) { NetPrintf(("connapi: [%p] internally disabling demangler for mesh involving server-based game/voip topologies\n", pConnApi)); pConnApi->bDemanglerEnabled = FALSE; } /* for CC-assisted scenarios, we don't allow demangling for meshes with potentially more than 2 players because connapi serializes the demangling attempts internally, and multiple back-to-back failing demangling attempts can induce a long wait time that will result in some clients not even having a chance to attempt CC-assisted path. */ else if ((pConnApi->ClientList.iMaxClients > 2) && (pConnApi->iCcMode != CONNAPI_CCMODE_PEERONLY)) { NetPrintf(("connapi: [%p] internally disabling demangler for CC-assisted mesh with a max player count (%d) larger than 2\n", pConnApi, pConnApi->ClientList.iMaxClients)); pConnApi->bDemanglerEnabled = FALSE; } #endif // create demangler if ((pConnApi->bDemanglerEnabled) && (pConnApi->pProtoMangle == NULL)) { #if CONNAPI_XBOX_NETWORKING NetPrintf(("connapi: [%p] creating demangler ref with max clients = %d\n", pConnApi, pConnApi->ClientList.iMaxClients)); if ((pConnApi->pProtoMangle = ProtoMangleCreate(pConnApi->strDemanglerServer, pConnApi->ClientList.iMaxClients, pConnApi->strGameName, "")) == NULL) #else NetPrintf(("connapi: [%p] creating demangler ref with gamename=%s and server=%s\n", pConnApi, pConnApi->strGameName, pConnApi->strDemanglerServer)); if ((pConnApi->pProtoMangle = ProtoMangleCreate(pConnApi->strDemanglerServer, PROTOMANGLE_PORT, pConnApi->strGameName, "")) == NULL) #endif { NetPrintf(("connapi: [%p] unable to create ProtoMangle module\n", pConnApi)); pConnApi->bDemanglerEnabled = FALSE; } else { // now that we have a valid ProtoMangle, immediately make sure that ConnApi-driven demangler timeout is passed down to it ConnApiControl(pConnApi, 'dtim', pConnApi->iConfigMnglTimeout, 0, NULL); ConnApiControl(pConnApi, 'dtif', pConnApi->iConfigMnglTimeoutFailover, 0, NULL); } } // create tunnel module if ((pConnApi->bTunnelEnabled) && (pConnApi->pProtoTunnel == NULL)) { if ((pConnApi->pProtoTunnel = ProtoTunnelCreate(pConnApi->ClientList.iMaxClients-1, pConnApi->iTunnelPort)) == NULL) { // unable to create, so disable the tunnel pConnApi->bTunnelEnabled = FALSE; } else { // we own the tunnel pConnApi->bTunnelOwner = TRUE; } } // set voip/gamelink timeouts ConnApiControl(pConnApi, 'time', pConnApi->iTimeout, 0, NULL); #if CONNAPI_XBOX_NETWORKING // set external session name and scid (calls into ProtoMangle) ConnApiControl(pConnApi, 'exsn', 0, 0, pConnApi->strExternalSessionName); ConnApiControl(pConnApi, 'exst', 0, 0, pConnApi->strExternalSessionTemplateName); ConnApiControl(pConnApi, 'scid', 0, 0, pConnApi->strScid); #endif ds_snzprintf(strAdvt, sizeof(strAdvt), "%u", pConnApi->uSelfId); // start advertising if (pConnApi->pGameUtilRef != NULL) { NetGameUtilAdvert(pConnApi->pGameUtilRef, pConnApi->strGameName, strAdvt, ""); } // leave memory group DirtyMemGroupLeave(); } /*F********************************************************************************/ /*! \Function ConnApiDestroy \Description Destroy the module state. \Input *pConnApi - pointer to module state \Version 01/04/2005 (jbrookes) */ /********************************************************************************F*/ void ConnApiDestroy(ConnApiRefT *pConnApi) { // disconnect ConnApiDisconnect(pConnApi); // remove idle handler NetConnIdleDel(_ConnApiIdle, pConnApi); VoipGroupDestroy(pConnApi->pVoipGroupRef); // destroy advertising gameutil ref if (pConnApi->pGameUtilRef != NULL) { NetGameUtilDestroy(pConnApi->pGameUtilRef); } // destroy tunnel, if present and we are the owner if ((pConnApi->pProtoTunnel != NULL) && (pConnApi->bTunnelOwner == TRUE)) { ProtoTunnelDestroy(pConnApi->pProtoTunnel); } // destroy demangler if (pConnApi->pProtoMangle != NULL) { ProtoMangleDestroy(pConnApi->pProtoMangle); } // release module memory DirtyMemFree(pConnApi, CONNAPI_MEMID, pConnApi->iMemGroup, pConnApi->pMemGroupUserData); } /*F********************************************************************************/ /*! \Function ConnApiConnect \Description Connect to a game. \Input *pConnApi - pointer to module state \Input *pClientList - list of clients in game session \Input iClientListSize - number of clients in list \Input iGameHostIndex - index in the client list who will be serving as game host \Input iVoipHostIndex - index in the clinet list who will be serving as voip host \Input iSessId - unique session identifier \Notes ConnApi supports invalid entries in the client list. Invalid clients are detected with a DirtyAddr that is zeroed out \Version 09/29/2009 (cvienneau) */ /********************************************************************************F*/ void ConnApiConnect(ConnApiRefT *pConnApi, ConnApiClientInfoT *pClientList, int32_t iClientListSize, int32_t iGameHostIndex, int32_t iVoipHostIndex, int32_t iSessId) { NetPrintf(("connapi: [%p] ConnApiConnect() called with listSize=%d, gamehost=%d, voiphost=%d, sessionId=%d\n", pConnApi, iClientListSize, iGameHostIndex, iVoipHostIndex, iSessId)); // make sure we're idle if (pConnApi->eState != ST_IDLE) { NetPrintf(("connapi: [%p] can't host or connect to a game when not in idle state\n", pConnApi)); return; } // save session identifier pConnApi->iSessId = iSessId; // virtualize ports if tunneling if (pConnApi->bTunnelEnabled == TRUE) { NetConnControl('vadd', pConnApi->uGamePort, 0, NULL, NULL); if (pConnApi->eVoipTopology != CONNAPI_VOIPTOPOLOGY_DISABLED) { NetConnControl('vadd', pConnApi->uVoipPort, 0, NULL, NULL); } } // initialize the game / voip host index only in topologies they apply if ((pConnApi->eGameTopology == CONNAPI_GAMETOPOLOGY_PEERHOSTED) || (pConnApi->eGameTopology == CONNAPI_GAMETOPOLOGY_SERVERHOSTED)) { pConnApi->iGameHostIndex = iGameHostIndex; } if (pConnApi->eVoipTopology == CONNAPI_VOIPTOPOLOGY_SERVERHOSTED) { pConnApi->iVoipHostIndex = iVoipHostIndex; } // init client list _ConnApiInitClientList(pConnApi, pClientList, iClientListSize); // let the voipgroup know if we are hosting or not VoipGroupControl(pConnApi->pVoipGroupRef, 'serv', (pConnApi->eVoipTopology == CONNAPI_VOIPTOPOLOGY_SERVERHOSTED), 0, NULL); #if CONNAPI_XBOX_NETWORKING if (pConnApi->bDemanglerEnabled == TRUE) { // let protomanglexboxone know what our index is ProtoMangleControl(pConnApi->pProtoMangle, 'self', pConnApi->iSelf, 0, NULL); } #endif // check for advertisement if necessary if (pConnApi->pGameUtilRef != NULL) { _ConnApiCheckAdvert(pConnApi); } pConnApi->eState = ST_INGAME; } /*F********************************************************************************/ /*! \Function ConnApiMigrateGameHost \Description Reopen all connections, using the host specified. This is for host migration to a different host in non-peerweb, needing new connections for everyone. \Input *pConnApi - pointer to module state \Input iNewGameHostIndex - index of the new game host \Version 09/21/2007 (jrainy) */ /********************************************************************************F*/ void ConnApiMigrateGameHost(ConnApiRefT *pConnApi, int32_t iNewGameHostIndex) { ConnApiClientT *pClient; int32_t iClientIndex; ConnApiConnStatusE eStatus; pConnApi->iGameHostIndex = iNewGameHostIndex; pConnApi->eState = ST_INGAME; for (iClientIndex = 0; iClientIndex < pConnApi->ClientList.iMaxClients; iClientIndex++) { pClient = &pConnApi->ClientList.Clients[iClientIndex]; if (pClient->bAllocated && (pClient->GameInfo.eStatus != CONNAPI_STATUS_ACTV)) { eStatus = pClient->GameInfo.eStatus; #if CONNAPI_XBOX_NETWORKING _ConnApiInitClientConnectionState(pConnApi, pClient, iClientIndex, CONNAPI_CONNFLAG_GAMECONN); #else pClient->GameInfo.eStatus = CONNAPI_STATUS_INIT; #endif _ConnApiUpdateCallback(pConnApi, iClientIndex, CONNAPI_CBTYPE_GAMEEVENT, eStatus, (ConnApiConnStatusE)pClient->GameInfo.eStatus); } } } /*F********************************************************************************/ /*! \Function ConnApiAddClient \Description Add a new client to a pre-existing game at the specified index. \Input *pConnApi - pointer to module state \Input *pClientInfo - info on joining user \Input iClientIndex - index to add client to \Output 0 if successful, error code otherwise. \Notes This function should be called by all current members of a game while ConnApiConnect() is called by the joining client. \Version 06/16/2008 (jbrookes) */ /********************************************************************************F*/ int32_t ConnApiAddClient(ConnApiRefT *pConnApi, ConnApiClientInfoT *pClientInfo, int32_t iClientIndex) { ConnApiClientT *pClient; // make sure we're not idle if (pConnApi->eState == ST_IDLE) { NetPrintf(("connapi: [%p] can't add a connection to a game in idle state\n", pConnApi)); return(CONNAPI_ERROR_INVALID_STATE); } // make sure there is room if (pConnApi->ClientList.iNumClients == pConnApi->ClientList.iMaxClients) { NetPrintf(("connapi: [%p] can't add a connection to the game because it is full\n", pConnApi)); return(CONNAPI_ERROR_CLIENTLIST_FULL); } // make sure the selected slot is valid if ((iClientIndex < 0) || (iClientIndex >= pConnApi->ClientList.iMaxClients)) { NetPrintf(("connapi: [%p] can't add a connection to the game in slot %d because valid slot range 0-%d\n", pConnApi, iClientIndex, pConnApi->ClientList.iMaxClients-1)); return(CONNAPI_ERROR_SLOT_OUT_OF_RANGE); } // get pointer to new client structure to fill in, and increment client count pClient = &pConnApi->ClientList.Clients[iClientIndex]; // check slot and make sure it is uninitialized if (pClient->bAllocated == TRUE) { NetPrintf(("connapi: [%p] slot %d already allocated; cannot add a new client in this slot\n", pConnApi, iClientIndex)); return(CONNAPI_ERROR_SLOT_USED); } // add client to list _ConnApiInitClient(pConnApi, pClient, pClientInfo, iClientIndex); // display client info #if DIRTYCODE_LOGGING NetPrintf(("connapi: [%p] adding client to clientlist\n", pConnApi)); _ConnApiDisplayClientInfo(&pConnApi->ClientList.Clients[iClientIndex], iClientIndex); #endif // increment client count pConnApi->ClientList.iNumClients += 1; // check for advertisement if necessary if (pConnApi->pGameUtilRef != NULL) { _ConnApiCheckAdvert(pConnApi); } return(0); } /*F********************************************************************************/ /*! \Function ConnApiFindClient \Description Returns the ConnApiClientT of a given client, if found by id. \Input *pConnApi - pointer to module state \Input *pClientInfo - info on searched user \Input *pOutClient - used to return the ClientT structure of the client \Output uint8_t - TRUE if the client is found, FALSE otherwise \Version 06/05/2008 (jbrookes) */ /********************************************************************************F*/ uint8_t ConnApiFindClient(ConnApiRefT *pConnApi, ConnApiClientInfoT *pClientInfo, ConnApiClientT *pOutClient) { int32_t iClient; for (iClient = 0; iClient < pConnApi->ClientList.iMaxClients; iClient++) { if (pConnApi->ClientList.Clients[iClient].ClientInfo.uId == pClientInfo->uId) { ds_memcpy_s(pOutClient, sizeof(*pOutClient), &pConnApi->ClientList.Clients[iClient], sizeof(pConnApi->ClientList.Clients[iClient])); return(TRUE); } } return(FALSE); } /*F********************************************************************************/ /*! \Function ConnApiRemoveClient \Description Remove a current client from a game. \Input *pConnApi - pointer to module state \Input iClientIndex - index of client to remove (used if pClientName is NULL) \Notes If this function is called inside of a ConnApi callback, the removal will be deferred until the next time NetConnIdle() is called. Otherwise, the removal will happen immediately. \Version 06/14/2008 (jbrookes) */ /********************************************************************************F*/ void ConnApiRemoveClient(ConnApiRefT *pConnApi, int32_t iClientIndex) { // make sure the select slot is valid if ((iClientIndex < 0) || (iClientIndex >= pConnApi->ClientList.iMaxClients)) { NetPrintf(("connapi: [%p] can't remove a connection from the game in slot %d because valid slot range is 0-%d\n", pConnApi, iClientIndex, pConnApi->ClientList.iMaxClients-1)); return; } _ConnApiRemoveClientSetup(pConnApi, iClientIndex, CONNAPI_CLIENTFLAG_REMOVE); } /*F********************************************************************************/ /*! \Function ConnApiDisconnect \Description Stop game, disconnect from clients, and reset client list. \Input *pConnApi - pointer to module state \Notes Any NetGameDistRefs created by the application that references a NetGameUtil/ NeGameLink combination created by ConnApi must destroy the DistRef(s) before calling this function. \Version 01/04/2005 (jbrookes) */ /********************************************************************************F*/ void ConnApiDisconnect(ConnApiRefT *pConnApi) { ConnApiClientT *pClient; int32_t iClient; NetPrintf(("connapi: [%p] disconnecting\n", pConnApi)); // make sure we're not idle if (pConnApi->eState == ST_IDLE) { NetPrintf(("connapi: [%p] can't disconnect when in idle state\n", pConnApi)); return; } // walk client list for (iClient = 0; iClient < pConnApi->ClientList.iMaxClients; iClient++) { // ref client pClient = &pConnApi->ClientList.Clients[iClient]; // if it's not us and was allocated, disconnect from them if ((iClient != pConnApi->iSelf) && pClient->bAllocated) { _ConnApiDisconnectClient(pConnApi, &pConnApi->ClientList.Clients[iClient], iClient, "disconnect"); } } // reset client list pConnApi->ClientList.iNumClients = 0; ds_memclr(&pConnApi->ClientList.Clients, pConnApi->ClientList.iMaxClients * sizeof(ConnApiClientT)); // devirtualize ports if tunneling is enabled if (pConnApi->bTunnelEnabled == TRUE) { NetConnControl('vdel', pConnApi->uGamePort, 0, NULL, NULL); if (pConnApi->eVoipTopology != CONNAPI_VOIPTOPOLOGY_DISABLED) { NetConnControl('vdel', pConnApi->uVoipPort, 0, NULL, NULL); } } // clear socket refs pConnApi->uTunlSockRef = 0; pConnApi->uGameSockRef = 0; pConnApi->uVoipSockRef = 0; // go to idle state pConnApi->eState = ST_IDLE; } /*F********************************************************************************/ /*! \Function ConnApiGetClientList \Description Get a list of current connections. \Input *pConnApi - pointer to module state \Output ConnApiClientListT * - pointer to client list \Version 01/04/2005 (jbrookes) */ /********************************************************************************F*/ const ConnApiClientListT *ConnApiGetClientList(ConnApiRefT *pConnApi) { return(&pConnApi->ClientList); } /*F********************************************************************************/ /*! \Function ConnApiStatus \Description Get status information. \Input *pConnApi - pointer to module state \Input iSelect - status selector \Input *pBuf - [out] storage for selector-specific output \Input iBufSize - size of output buffer \Output int32_t - selector specific \Notes iSelect can be one of the following: \verbatim 'cbfp' - return current callback function pointer in output buffer 'cbup' - return current callback data pointer in output buffer 'ctim' - returns the connection timeout 'dtim' - get effective demangler timeout (in milliseconds) 'ghst' - return the the host index ConnApiClientT (via pBuf) for the game host in peer hosted and server hosted games 'gprt' - return game port 'gsrv' - return whether the game is server hosted (ConnApiClientT returned via pBuf) 'ingm' - currently 'in game' (connecting to or connected to one or more peers) 'lbuf' - returns GameLink buffer allocation size 'lclt' - returns one past the index of the last allocated player 'lcon' - returns the number of consoles (excluding the game server) allocated in the client list 'minp' - returns GameLink input buffer queue length (zero=default) 'mngl' - returns whether demangler is enabled or not 'mout' - returns GameLink output buffer queue length (zero=default) 'mplr' - *deprecated - replaced by 'lclt'* returns one past the index of the last allocated player 'mwid' - returns GameLink max packet size (zero=default) 'nmsk' - returns current netmask 'peer' - returns game conn peer-web enable/disable status 'self' - returns index of local user in client list 'sock' - copy socket ref to pBuf 'sess' - copies session information into output buffer 'time' - returns the timeout 'tprt' - returns port tunnel has been bound to (if available) 'tref' - returns the prototunnel ref used 'tunl' - returns whether tunnel is enabled or not 'type' - returns current connection type (CONNAPI_CONNFLAG_*) 'vhst' - return the host index and ConnApiClientT (via pBuf) for the voip host in server hosted voip 'vprt' - return voip port \endverbatim \Version 01/04/2005 (jbrookes) */ /********************************************************************************F*/ int32_t ConnApiStatus(ConnApiRefT *pConnApi, int32_t iSelect, void *pBuf, int32_t iBufSize) { return(ConnApiStatus2(pConnApi, iSelect, NULL, pBuf, iBufSize)); } /*F********************************************************************************/ /*! \Function ConnApiStatus2 \Description Get status information. \Input *pConnApi - pointer to module state \Input iSelect - status selector \Input *pData - input data \Input *pBuf - [out] storage for selector-specific output \Input iBufSize - size of output buffer \Output int32_t - selector specific \Notes iSelect can be one of the following: \verbatim 'cadr' - stands for Connection Address, ip address used at the platform socket level to reach this client (regardless of tunneling being used or not) 'cbfp' - return current callback function pointer in output buffer 'cbup' - return current callback data pointer in output buffer 'cprt' - stands for Connection Port, UDP port used at the platform socket level to reach this client 'ctim' - returns the connection timeout 'dtim' - get effective demangler timeout (in milliseconds) 'ghst' - return the the host index ConnApiClientT (via pBuf) for the game host in peer hosted and server hosted games 'gprt' - return game port 'gsrv' - return whether the game is server hosted (ConnApiClientT returned via pBuf) 'host' - returns whether hosting or not, plus copies host name to buffer 'ingm' - currently 'in game' (connecting to or connected to one or more peers) 'lbuf' - returns GameLink buffer allocation size 'lclt' - returns one past the index of the last allocated player 'lcon' - returns the number of consoles (excluding the game server) allocated in the client list 'minp' - returns GameLink input buffer queue length (zero=default) 'mngl' - returns whether demangler is enabled or not 'mout' - returns GameLink output buffer queue length (zero=default) 'mplr' - *deprecated - replaced by 'lclt'* returns one past the index of the last allocated player 'mvtm' - return true if multiple virtual machine mode is active 'mwid' - returns GameLink max packet size (zero=default) 'nmsk' - returns current netmask 'peer' - returns game conn peer-web enable/disable status 'self' - returns index of local user in client list 'sock' - copy socket ref to pBuf 'sess' - copies session information into output buffer 'time' - returns the timeout 'tprt' - returns port tunnel has been bound to (if available) 'tref' - returns the prototunnel ref used 'tunl' - returns whether tunnel is enabled or not 'tunr' - returns receive protunnel stats as a ProtoTunnelStatT in pBuf for a given client pData 'tuns' - returns send prototunnel stats as a ProtoTunnelStatT in pBuf for a given client pData 'type' - returns current connection type (CONNAPI_CONNFLAG_*) 'vgrp' - returns voipgroup pointer in pBuf 'vhst' - return the host index and ConnApiClientT (via pBuf) for the voip host in server hosted voip 'vprt' - return voip port \endverbatim \Version 01/04/2005 (jbrookes) */ /********************************************************************************F*/ int32_t ConnApiStatus2(ConnApiRefT *pConnApi, int32_t iSelect, void *pData, void *pBuf, int32_t iBufSize) { if ((iSelect == 'cadr') && (pBuf != NULL) && (iBufSize >= (signed)sizeof(uint32_t))) { uint8_t bLocalAddr; ConnApiClientT *pClientUsed; ConnApiClientT *pClient = (ConnApiClientT *)pData; struct sockaddr addr; *(uint32_t*)pBuf = _ConnApiGetConnectAddr(pConnApi, pClient, &bLocalAddr, CONNAPI_CONNFLAG_GAMECONN, &pClientUsed); // if we are using the tunnel return the tunnel address if (pConnApi->pProtoTunnel != NULL) { ProtoTunnelStatus(pConnApi->pProtoTunnel, 'vtop', pClient->iTunnelId, &addr, sizeof(addr)); *(uint32_t*)pBuf = SockaddrInGetAddr(&addr); } if (pClientUsed->GameInfo.eStatus != CONNAPI_STATUS_ACTV) { return(-1); } return(0); } if ((iSelect == 'cbfp') && (pBuf != NULL) && (iBufSize >= (signed)sizeof(pConnApi->pCallback[0]))) { ds_memcpy(pBuf, &(pConnApi->pCallback[0]), sizeof(pConnApi->pCallback[0])); return(0); } if ((iSelect == 'cbup') && (pBuf != NULL) && (iBufSize >= (signed)sizeof(pConnApi->pUserData[0]))) { ds_memcpy(pBuf, &(pConnApi->pUserData[0]), sizeof(pConnApi->pUserData[0])); return(0); } if ((iSelect == 'cprt') && (pBuf != NULL) && (iBufSize >= (signed)sizeof(uint16_t))) { uint8_t bLocalAddr; ConnApiClientT *pClientUsed; ConnApiClientT *pClient = (ConnApiClientT *)pData; _ConnApiGetConnectAddr(pConnApi, pClient, &bLocalAddr, CONNAPI_CONNFLAG_GAMECONN, &pClientUsed); if (pClientUsed->GameInfo.eStatus != CONNAPI_STATUS_ACTV) { return(-1); } if (pClientUsed->iTunnelId > 0) { ProtoTunnelStatus(pConnApi->pProtoTunnel, 'rprt', pClientUsed->iTunnelId, pBuf, iBufSize); } else { *(uint16_t*)pBuf = pClientUsed->GameInfo.uMnglPort; } return(0); } if (iSelect == 'ctim') { return(pConnApi->iConnTimeout); } if (iSelect == 'dtim') { return(pConnApi->iCurrentMnglTimeout); } if ((iSelect == 'ghst') && (pBuf != NULL) && (iBufSize >= (signed)sizeof(ConnApiClientT))) { if ((pConnApi->eGameTopology == CONNAPI_GAMETOPOLOGY_PEERHOSTED) || (pConnApi->eGameTopology == CONNAPI_GAMETOPOLOGY_SERVERHOSTED)) { ds_memcpy(pBuf, &pConnApi->ClientList.Clients[pConnApi->iGameHostIndex], sizeof(ConnApiClientT)); return(pConnApi->iGameHostIndex); } return(-1); } if (iSelect == 'gprt') { return(pConnApi->uGamePort); } if (iSelect == 'gsrv') { uint8_t bHostIsGameServer; if ((bHostIsGameServer = (pConnApi->eGameTopology == CONNAPI_GAMETOPOLOGY_SERVERHOSTED)) == TRUE) { if ((pBuf != NULL) && (iBufSize >= (signed)sizeof(ConnApiClientT))) { ds_memcpy(pBuf, &pConnApi->ClientList.Clients[pConnApi->iGameHostIndex], sizeof(ConnApiClientT)); } } return(bHostIsGameServer); } if (iSelect == 'ingm') { return(pConnApi->eState != ST_IDLE); } if (iSelect == 'lbuf') { return(pConnApi->iLinkBufSize); } if (iSelect == 'lcon') { int32_t iNumberPlayers = ConnApiStatus2(pConnApi, 'lclt', pData, pBuf, iBufSize); // if the game is server hosted we do not want to include the server if (ConnApiStatus(pConnApi, 'gsrv', NULL, 0)) { --iNumberPlayers; } return(iNumberPlayers); } if (iSelect == 'minp') { return(pConnApi->iGameMinp); } if (iSelect == 'mngl') { return(pConnApi->bDemanglerEnabled); } if (iSelect == 'mout') { return(pConnApi->iGameMout); } if ((iSelect == 'mplr') || (iSelect == 'lclt')) { int32_t iClient; int32_t iNumberPlayers = 0; for (iClient = 0; iClient < pConnApi->ClientList.iMaxClients; iClient++) { if (pConnApi->ClientList.Clients[iClient].bAllocated) { iNumberPlayers = iClient + 1; } } return(iNumberPlayers); } if (iSelect == 'mwid') { return(pConnApi->iGameMwid); } if (iSelect == 'nmsk') { return(pConnApi->uNetMask); } if (iSelect == 'peer') { return(pConnApi->eGameTopology == CONNAPI_GAMETOPOLOGY_PEERWEB); } if (iSelect == 'self') { return(pConnApi->iSelf); } if (iSelect == 'sess') { ds_strnzcpy((char *)pBuf, pConnApi->strSession, iBufSize); return(0); } if (iSelect == 'sock') { if (iBufSize >= (signed)sizeof(intptr_t)) { if (pConnApi->bTunnelEnabled) { ProtoTunnelStatus(pConnApi->pProtoTunnel, 'sock', 0, pBuf, iBufSize); } else if ((pConnApi->iGameHostIndex >= 0) && (pConnApi->ClientList.Clients[pConnApi->iGameHostIndex].pGameUtilRef != NULL)) { NetGameUtilStatus(pConnApi->ClientList.Clients[pConnApi->iGameHostIndex].pGameUtilRef, 'sock', pBuf, iBufSize); } else { NetPrintf(("connapi: [%p] ConnApiStatus('sock') failed because game socket not yet available (tunnel enabled = %s)\n", pConnApi, pConnApi->bTunnelEnabled?"true":"false")); return(-2); } return(0); } else { NetPrintf(("connapi: [%p] ConnApiStatus('sock') failed because size (%d) of user-provided buffer is too small (required: %d)\n", pConnApi, iBufSize, sizeof(intptr_t))); return(-1); } } if (iSelect == 'time') { return(pConnApi->iTimeout); } if ((iSelect == 'tprt') && (pConnApi->pProtoTunnel != NULL)) { return(ProtoTunnelStatus(pConnApi->pProtoTunnel, 'lprt', 0, NULL, 0)); } if (iSelect == 'tref' && (pConnApi->pProtoTunnel != NULL)) { if (iBufSize >= (signed)sizeof(ProtoTunnelRefT*)) { ds_memcpy(pBuf, &pConnApi->pProtoTunnel, sizeof(ProtoTunnelRefT*)); return(0); } else { NetPrintf(("connapi: [%p] ConnApiStatus('tref') failed because size (%d) of user-provided buffer is too small (required: %d)\n", pConnApi, iBufSize, sizeof(ProtoTunnelRefT*))); return(-1); } } if (iSelect == 'tunl') { return(pConnApi->bTunnelEnabled); } if (iSelect == 'tunr') { if ((pData != NULL) && (pBuf != NULL) && (iBufSize == sizeof(ProtoTunnelStatT))) { ConnApiClientT *pClient = (ConnApiClientT *)pData; ProtoTunnelStatus(pConnApi->pProtoTunnel, 'rcvs', pClient->iTunnelId, pBuf, iBufSize); return(0); } else { NetPrintf(("connapi: [%p] ConnApiStatus('tunr') failed due to invalid arguments\n", pConnApi)); return(-1); } } if (iSelect == 'tuns') { if ((pData != NULL) && (pBuf != NULL) && (iBufSize == sizeof(ProtoTunnelStatT))) { ConnApiClientT *pClient = (ConnApiClientT *)pData; ProtoTunnelStatus(pConnApi->pProtoTunnel, 'snds', pClient->iTunnelId, pBuf, iBufSize); return(0); } else { NetPrintf(("connapi: [%p] ConnApiStatus('tuns') failed due to invalid arguments\n", pConnApi)); return(-1); } } if (iSelect == 'type') { return(pConnApi->uConnFlags); } if (iSelect == 'ulmt') { return(pConnApi->iGameUnackLimit); } if (iSelect == 'vgrp') { if (iBufSize >= (int32_t)sizeof(pConnApi->pVoipGroupRef)) { ds_memcpy(pBuf, &pConnApi->pVoipGroupRef, sizeof(pConnApi->pVoipGroupRef)); return(0); } return(-1); } if ((iSelect == 'vhst') && (pBuf != NULL) && (iBufSize >= (signed)sizeof(ConnApiClientT))) { if (pConnApi->eVoipTopology == CONNAPI_VOIPTOPOLOGY_SERVERHOSTED) { ds_memcpy(pBuf, &pConnApi->ClientList.Clients[pConnApi->iVoipHostIndex], sizeof(ConnApiClientT)); return(pConnApi->iVoipHostIndex); } return(-1); } if (iSelect == 'vprt') { return(pConnApi->uVoipPort); } // unhandled return(-1); } /*F********************************************************************************/ /*! \Function ConnApiControl \Description Control behavior of module. \Input *pConnApi - pointer to module state \Input iControl - status selector \Input iValue - control value \Input iValue2 - control value \Input *pValue - control value \Output int32_t - selector specific \Notes iControl can be one of the following: \verbatim 'adve' - set to enable ProtoAdvt advertising 'auto' - set auto-update enable/disable - iValue=TRUE or FALSE (default TRUE) 'cbfp' - set callback function pointer - pValue=pCallback 'cbup' - set callback user data pointer - pValue=pUserData 'ccmd' - set the CC mode (CONNAPI_CCMODE_*) 'ctim' - set connection timeout - iValue=timeout (minimum & default 10000 ms) 'dist' - set dist ref - iValue=index of client or 'gsrv' for host user, pValue=dist ref 'dsrv' - set demangler server - pValue=pointer to demangler server name (default demangler.ea.com) 'dtif' - set demangler timeout for scenarios involving or CC assistance - iValue=timeout in milliseconds 'dtim' - set demangler timeout - iValue=timeout in milliseconds 'exsn' - set the external session name for the MultiplayerSessionReference (xbox one only) 'exst' - set the external session template name for the MultiplayerSessionReference (xbox one only) 'estv' - enable flag to establish voip for a client after it was delayed, iValue=client index 'gprt' - set game port to use - iValue=port 'lbuf' - set game link buffer size - iValue=size (default 1024) 'lqos' - set QoS packet size used when creating NetGameLinks. iValue=packet size in bytes 'maxg' - set maximum number of voipgroups we support 'meta' - enable/disable commudp metadata 'minp' - set GameLink input buffer queue length - iValue=length (default 32) 'mngl' - set demangler enable/disable - iValue=TRUE/FALSE (default TRUE) 'mout' - set GameLink output buffer queue length - iValue=length (default 32) 'mvtm' - set multiple virtual machine mode enable/disable - iValue=TRUE/FALSE (default FALSE) 'mwid' - set GameLink max packet size - iValue=size (default NETGAME_DATAPKT_DEFSIZE, max NETGAME_DATAPKT_MAXSIZE) 'nmsk' - set netmask used for external address comparisons - iValue=mask (default 0xffffffff) 'rcbk' - set enable of disc callback on removal - iValue=TRUE/FALSE (default FALSE) 'scid' - set the service configuration id for the MultiplayerSessionReference (xbox one only) 'sqos' - set QoS settings used when creating NetGameLinks. iValue=QoS duration (0 disables QoS), iValue2=QoS packet interval 'stun' - set prototunnel ref 'tctl' - set prototunnel control data 'time' - set timeout - iValue=timeout in ms (default 15 seconds) 'tgam' - enable the override of game tunnel flags - iValue=falgs, iValue2=boolean(overrides or not) 'tunl' - set tunnel parms: iValue = TRUE/FALSE to enable/disable or negative to ignore iValue2 = tunnel port, or negative to ignore 'type' - set connection type - iValue = CONNAPI_CONNFLAG_* 'voig' - pass-through to VoipGroupControl() - 'getr' VoipGroupControl selector has been deprecated please switch to ConnApiStatus('vgrp') instead 'voip' - pass-through to VoipControl() - iValue=VoIP iControl, iValue2= VoIP iValue 'vprt' - set voip port to use - iValue=port 'vset' - set voip enable/disable (default=TRUE; call before ConnApiOnline()) '!res' - force secure address resolution to fail, to simulate p2p connection failure. iValue = TRUE/FALSE to enable/disable iValue2 = index of user to force fail or -1 for all users or local index for all users \endverbatim \Version 01/04/2005 (jbrookes) */ /********************************************************************************F*/ int32_t ConnApiControl(ConnApiRefT *pConnApi, int32_t iControl, int32_t iValue, int32_t iValue2, void *pValue) { if (iControl == 'adve') { #if CONNAPI_XBOX_NETWORKING NetPrintf(("connapi: ProtoAdvt advertising cannot be enabled on Xbox One\n")); #else NetPrintf(("connapi: [%p] ProtoAdvt advertising %s\n", pConnApi, (iValue?"enabled":"disabled"))); pConnApi->bDoAdvertising = iValue; // if disabling advertising and we already have an advertising ref, kill it if ((pConnApi->bDoAdvertising == FALSE) && (pConnApi->pGameUtilRef != NULL)) { NetGameUtilDestroy(pConnApi->pGameUtilRef); pConnApi->pGameUtilRef = NULL; } #endif return(0); } if (iControl == 'auto') { pConnApi->bAutoUpdate = iValue; return(0); } if (iControl == 'ccmd') { #if DIRTYCODE_LOGGING static const char *_ConnApiCcModeNames[] = { "CONNAPI_CCMODE_PEERONLY", "CONNAPI_CCMODE_HOSTEDONLY", "CONNAPI_CCMODE_HOSTEDFALLBACK" }; #endif switch (iValue) { case (CONNAPI_CCMODE_PEERONLY): VoipGroupControl(pConnApi->pVoipGroupRef, 'ccmd', VOIPGROUP_CCMODE_PEERONLY, 0, NULL); break; case (CONNAPI_CCMODE_HOSTEDONLY): VoipGroupControl(pConnApi->pVoipGroupRef, 'ccmd', VOIPGROUP_CCMODE_HOSTEDONLY, 0, NULL); break; case (CONNAPI_CCMODE_HOSTEDFALLBACK): VoipGroupControl(pConnApi->pVoipGroupRef, 'ccmd', VOIPGROUP_CCMODE_HOSTEDFALLBACK, 0, NULL); break; default: NetPrintf(("connapi: [%p] unsupported CC mode %d\n", pConnApi, iValue)); return(-1); } NetPrintf(("connapi: [%p] CC mode = %d (%s)\n", pConnApi, iValue, _ConnApiCcModeNames[iValue])); pConnApi->iCcMode = iValue; return(0); } if (iControl == 'cbfp') { // set callback function pointer pConnApi->pCallback[0] = ((pValue != NULL) ? (ConnApiCallbackT *)pValue : _ConnApiDefaultCallback); return(0); } if (iControl == 'cbup') { // set callback user data pointer pConnApi->pUserData[0] = pValue; return(0); } if ((iControl == 'ctim') && (iValue >= CONNAPI_CONNTIMEOUT_DEFAULT)) { NetPrintf(("connapi: [%p] setting connection timeout to %d\n", pConnApi, iValue)); pConnApi->iConnTimeout = iValue; return(0); } if (iControl == 'dist') { // set dist ref for specified client // special value to signify the host if (iValue == 'gsrv') { iValue = pConnApi->iGameHostIndex; } if ((iValue >= 0) && (iValue < pConnApi->ClientList.iMaxClients)) { pConnApi->ClientList.Clients[iValue].pGameDistRef = (NetGameDistRefT *)pValue; return(0); } } if (iControl == 'dsrv') { // set demangler server ds_strnzcpy(pConnApi->strDemanglerServer, (const char *)pValue, sizeof(pConnApi->strDemanglerServer)); return(0); } if ((iControl == 'dtim') || (iControl == 'dtif')) { // set demangler timeout int32_t iNewMnglTimeout; #if DIRTYCODE_LOGGING uint8_t bIsFailoverPossible = FALSE; #endif // special case to allow using a different timeout value for connapi involving CC assistance if (iControl == 'dtif') { NetPrintf(("connapi: [%p] changing demangler_with_failover timeout config (%d ms --> %d ms)\n", pConnApi, pConnApi->iConfigMnglTimeoutFailover, iValue)); pConnApi->iConfigMnglTimeoutFailover = iValue; } else { NetPrintf(("connapi: [%p] changing demangler timeout config (%d ms --> %d ms)\n", pConnApi, pConnApi->iConfigMnglTimeout, iValue)); pConnApi->iConfigMnglTimeout = iValue; } // check if the special timeout value for cc scenarios is applicable if (pConnApi->iCcMode != CONNAPI_CCMODE_PEERONLY) { #if DIRTYCODE_LOGGING bIsFailoverPossible = TRUE; #endif iNewMnglTimeout = pConnApi->iConfigMnglTimeoutFailover; } else { iNewMnglTimeout = pConnApi->iConfigMnglTimeout; } // pass new effective demangler timeout value down to ProtoMangle if ((pConnApi->pProtoMangle != NULL) && (iNewMnglTimeout != pConnApi->iCurrentMnglTimeout)) { NetPrintf(("connapi: [%p] applying new demangler timeout (%d ms --> %d ms) for a demangling scenario %s\n", pConnApi, pConnApi->iCurrentMnglTimeout, iNewMnglTimeout, (bIsFailoverPossible?"with":"without"))); pConnApi->iCurrentMnglTimeout = iNewMnglTimeout; ProtoMangleControl(pConnApi->pProtoMangle, 'time', pConnApi->iCurrentMnglTimeout, 0, NULL); } return(0); } #if CONNAPI_XBOX_NETWORKING if (iControl == 'exsn') { if ((pValue == NULL) || (*(char*)pValue == '\0')) { NetPrintf(("connapi: [%p] 'exsn', invalid external session name\n", pConnApi)); } else { if (pConnApi->strExternalSessionName != pValue) { ds_strnzcpy(pConnApi->strExternalSessionName, (char*)pValue, sizeof(pConnApi->strExternalSessionName)); NetPrintf(("connapi: [%p] 'exsn', external session name saved as (%s)\n", pConnApi, pConnApi->strExternalSessionName)); } if (pConnApi->pProtoMangle != NULL) { return(ProtoMangleControl(pConnApi->pProtoMangle, 'exsn', 0, 0, pConnApi->strExternalSessionName)); } return(0); } } if (iControl == 'exst') { if ((pValue == NULL) || (*(char*)pValue == '\0')) { NetPrintf(("connapi: [%p] 'exst', invalid external session template name\n", pConnApi)); } else { if (pConnApi->strExternalSessionTemplateName != pValue) { ds_strnzcpy(pConnApi->strExternalSessionTemplateName, (char*)pValue, sizeof(pConnApi->strExternalSessionTemplateName)); NetPrintf(("connapi: [%p] 'exst', external session template name saved as (%s)\n", pConnApi, pConnApi->strExternalSessionTemplateName)); } if (pConnApi->pProtoMangle != NULL) { return(ProtoMangleControl(pConnApi->pProtoMangle, 'exst', 0, 0, pConnApi->strExternalSessionTemplateName)); } return(0); } } #endif if (iControl == 'gprt') { NetPrintf(("connapi: [%p] using game port %d\n", pConnApi, iValue)); pConnApi->uGamePort = (uint16_t)iValue; return(0); } if (iControl == 'lbuf') { // set game link buffer size pConnApi->iLinkBufSize = iValue; return(0); } if (iControl == 'maxg') { // set maximum number of voipgroups NetPrintf(("connapi: changing maximum number of voipgroups from %d to %d\n", _ConnApi_iMaxVoipGroups, iValue)); _ConnApi_iMaxVoipGroups = (int8_t)iValue; return(0); } if (iControl == 'meta') { // enable/disable commudp metadata NetPrintf(("connapi: [%p] commudp metadata %s\n", pConnApi, iValue ? "enabled" : "disabled")); pConnApi->bCommUdpMetadata = iValue; return(0); } if (iControl == 'minp') { // set gamelink input packet queue length pConnApi->iGameMinp = iValue; return(0); } if (iControl == 'mngl') { #if CONNAPI_XBOX_NETWORKING NetPrintf(("connapi: [%p] demangler cannot be disabled on Xbox One\n", pConnApi)); #else // set demangler enable/disable NetPrintf(("connapi: [%p] demangling %s\n", pConnApi, iValue ? "enabled" : "disabled")); pConnApi->bDemanglerEnabled = iValue; #endif return(0); } if (iControl == 'mout') { // set gamelink output packet queue length pConnApi->iGameMout = iValue; return(0); } if (iControl == 'mwid') { // set gamelink packet length pConnApi->iGameMwid = iValue; return(0); } if (iControl == 'nmsk') { // set netmask pConnApi->uNetMask = (unsigned)iValue; return(0); } if (iControl == 'rcbk') { // enable disc callback on removal pConnApi->bRemoveCallback = iValue; return(0); } #if CONNAPI_XBOX_NETWORKING if (iControl == 'scid') { if ((pValue == NULL) || (*(char*)pValue == '\0')) { NetPrintf(("connapi: [%p] 'scid', invalid service configuration id\n", pConnApi)); } else { if (pConnApi->strScid != pValue) { ds_strnzcpy(pConnApi->strScid, (char*)pValue, sizeof(pConnApi->strScid)); NetPrintf(("connapi: [%p] 'scid', service configuration name saved as (%s)\n", pConnApi, pConnApi->strScid)); } if (pConnApi->pProtoMangle != NULL) { return(ProtoMangleControl(pConnApi->pProtoMangle, 'scid', 0, 0, pConnApi->strScid)); } return(0); } } #endif if ((iControl == 'stun') && (pConnApi->pProtoTunnel == NULL) && (pValue != NULL)) { // set prototunnel ref pConnApi->pProtoTunnel = (ProtoTunnelRefT *)pValue; return(0); } if (iControl == 'time') { // set timeout pConnApi->iTimeout = iValue; VoipGroupControl(pConnApi->pVoipGroupRef, 'time', iValue, 0, NULL); return(0); } if ((iControl == 'tctl') && (pConnApi->pProtoTunnel != NULL)) { return(ProtoTunnelControl(pConnApi->pProtoTunnel, iValue, iValue2, 0, pValue)); } if (iControl == 'tgam') { pConnApi->uGameTunnelFlag = iValue; pConnApi->uGameTunnelFlagOverride = iValue2; } if (iControl == 'tunl') { // set tunnel status if (iValue >= 0) { pConnApi->bTunnelEnabled = iValue; VoipGroupControl(pConnApi->pVoipGroupRef, 'tunl', iValue, 0, NULL); } if (iValue2 > 0) { pConnApi->iTunnelPort = iValue2; } return(0); } if (iControl == 'type') { // set connection flags (CONNAPI_CONNFLAG_*) NetPrintf(("connapi: [%p] connflag change from 0x%02x to 0x%02x\n", pConnApi, pConnApi->uConnFlags, iValue)); pConnApi->uConnFlags = (uint16_t)iValue; return(0); } if (iControl == 'ulmt') { // set gamelink unack window size pConnApi->iGameUnackLimit = iValue; return(0); } if (iControl == 'voig') { if (iValue == 'getr') //$$todo remove 'getr' support in future release { return(ConnApiStatus(pConnApi, 'vgrp', pValue, sizeof(pConnApi->pVoipGroupRef))); } VoipGroupControl(pConnApi->pVoipGroupRef, iValue, iValue2, 0, pValue); return(0); } if (iControl == 'voip') { if(pConnApi->pVoipRef != NULL) { VoipControl(pConnApi->pVoipRef, iValue, iValue2, pValue); return(0); } NetPrintf(("connapi: [%p] - WARNING - ConnApiControl(): processing of 'voip' selector failed because of an uninitialized VOIP module reference!\n", pConnApi)); } if (iControl == 'vprt') { NetPrintf(("connapi: [%p] using voip port %d\n", pConnApi, iValue)); pConnApi->uVoipPort = (uint16_t)iValue; return(0); } if (iControl == 'vset') { uint8_t bVoipEnabled; // if disabling VoIP, set game flags appropriately if ((bVoipEnabled = iValue) == FALSE) { NetPrintf(("connapi: [%p] 'vset' used to globally disable voip\n", pConnApi)); pConnApi->uConnFlags &= ~CONNAPI_CONNFLAG_VOIPCONN; } return(0); } #if DIRTYCODE_DEBUG if (iControl == '!res') { ConnApiClientT *pClient; if (iValue2 >= 0 && iValue2 < pConnApi->ClientList.iMaxClients) { pClient = &pConnApi->ClientList.Clients[iValue2]; if (pClient->bAllocated == TRUE) { if (iValue > 0) { NetPrintf(("connapi: [%p] setting debug FailP2P flag for user at index %d\n", pConnApi, iValue2)); pConnApi->ClientList.Clients[iValue2].uFlags |= CONNAPI_CLIENTFLAG_P2PFAILDBG; if (iValue2 == pConnApi->iSelf) { NetPrintf(("connapi: [%p] setting debug FailP2P flag with our own index, disabling P2P for all users for all users\n", pConnApi)); } } else { NetPrintf(("connapi: [%p] resetting debug FailP2P flag for user at index %d\n", pConnApi, iValue2)); pConnApi->ClientList.Clients[iValue2].uFlags &= ~CONNAPI_CLIENTFLAG_P2PFAILDBG; } } else { NetPrintf(("connapi: [%p] failed to set debug FailP2P flag for user at index %d\n", pConnApi, iValue2)); return(-1); } } else if (iValue2 < 0) { pConnApi->bFailP2PConnect = iValue; NetPrintf(("connapi: [%p] setting debug FailP2P flag to %d for all users\n", pConnApi, iValue)); } else { NetPrintf(("connapi: [%p] cannot set debug FailP2P flag to %d for user at index %d\n", pConnApi, iValue, iValue2)); } return(0); } #endif if (iControl == 'sqos') { pConnApi->iQosDuration = iValue; NetPrintf(("connapi: [%p] total duration of QoS characterization over netgamelinks --> %d ms %s\n", pConnApi, pConnApi->iQosDuration, ((pConnApi->iQosDuration == 0) ? "(QoS over NetGameLink disabled)" : ""))); pConnApi->iQosInterval = iValue2; NetPrintf(("connapi: [%p] send interval used for QoS characterization over netgamelinks --> %d ms\n", pConnApi, pConnApi->iQosInterval)); return(0); } if (iControl == 'lqos') { NetPrintf(("connapi: [%p] packet size used for QoS characterization over netgamelinks --> %d bytes\n", pConnApi, iValue)); pConnApi->iQosPacketSize = iValue; return(0); } if (iControl == 'estv') { ConnApiClientT *pClient; if (iValue < pConnApi->ClientList.iMaxClients) { pClient = &pConnApi->ClientList.Clients[iValue]; if (pClient->bAllocated == TRUE) { // no-op if it is already established // in the case of the local user, he will always have bEstablishVoip == TRUE so let's not spam with extra logs about it if (pClient->bEstablishVoip == FALSE) { NetPrintf(("connapi: [%p] activating delayed voip for client %d\n", pConnApi, iValue)); pClient->bEstablishVoip = TRUE; return(0); } } else { NetPrintf(("connapi: [%p] activating delayed voip failed because client %d not allocated\n", pConnApi, iValue)); return(-1); } } else { NetPrintf(("connapi: [%p] activating delayed voip failed because of client index of range\n", pConnApi)); return(-1); } } // unhandled return(-1); } /*F********************************************************************************/ /*! \Function ConnApiUpdate \Description Update the ConnApi module (must be called directly if auto-update is disabled) \Input *pConnApi - pointer to module state \Notes By default, ConnApiUpdate() is called internally via a NetConnIdle() callback (auto-update). If auto-update is disabled via ConnApiControl('auto'), ConnApiUpdate must be polled by the application instead. \Version 01/06/2005 (jbrookes) */ /********************************************************************************F*/ void ConnApiUpdate(ConnApiRefT *pConnApi) { // update client flags _ConnApiUpdateClientFlags(pConnApi); // update connapi connections _ConnApiUpdateConnections(pConnApi); #if CONNAPI_XBOX_NETWORKING /* update protomangle outside the demangling phase context required for protomangle to be pumped after a call to ProtoMangleControl('remv') */ if (pConnApi->bDemanglerEnabled == TRUE) { ProtoMangleUpdate(pConnApi->pProtoMangle); } #endif // handle removal of clients from client list, if requested _ConnApiUpdateRemoval(pConnApi); } /*F********************************************************************************/ /*! \Function ConnApiAddCallback \Description Register a new callback \Input *pConnApi - pointer to module state \Input *pCallback - the callback to add \Input *pUserData - the user data that will be passed back \Output int32_t - negative means error. 0 or greater: slot used \Version 09/18/2008 (jrainy) */ /********************************************************************************F*/ int32_t ConnApiAddCallback(ConnApiRefT *pConnApi, ConnApiCallbackT *pCallback, void *pUserData) { int32_t iIndex; // skip the first (0th, which is reserved for 'cbfp' and 'cbup' backward compatibility. for(iIndex = 1; iIndex < CONNAPI_MAX_CALLBACKS; iIndex++) { if (pConnApi->pCallback[iIndex] == NULL) { pConnApi->pCallback[iIndex] = pCallback; pConnApi->pUserData[iIndex] = pUserData; return(iIndex); } } return(CONNAPI_CALLBACKS_FULL); } /*F********************************************************************************/ /*! \Function ConnApiRemoveCallback \Description Unregister a callback \Input *pConnApi - pointer to module state \Input *pCallback - the callback to remove \Input *pUserData - the user data that was originally passed in \Output int32_t - negative means error. 0 or greater: slot freed \Version 09/18/2008 (jrainy) */ /********************************************************************************F*/ int32_t ConnApiRemoveCallback(ConnApiRefT *pConnApi, ConnApiCallbackT *pCallback, void *pUserData) { int32_t iIndex; // skip the first (0th, which is reserved for 'cbfp' and 'cbup' backward compatibility. for(iIndex = 1; iIndex < CONNAPI_MAX_CALLBACKS; iIndex++) { if ((pConnApi->pCallback[iIndex] == pCallback) && (pConnApi->pUserData[iIndex] == pUserData)) { pConnApi->pCallback[iIndex] = NULL; pConnApi->pUserData[iIndex] = NULL; return(iIndex); } } return(CONNAPI_CALLBACK_NOT_FOUND); }