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

4041 lines
158 KiB
C

/*H********************************************************************************/
/*!
\File voipconnection.c
\Description
VoIP virtual connection manager.
\Copyright
Copyright (c) Electronic Arts 2004. ALL RIGHTS RESERVED.
\Notes
\verbatim
None of the packet types used by DirtySDK's voip implementation is reliable, i.e. delivery to the other end of the connection is not guaranteed.
There exists some sort of reliability during the connection establishment handshaking: voip connect packets (VoipConnPacketT) are being re-sent
until packets are detected as received by the other end of the connection. This mechanism is specific to the connection establishment and is
not generic enough for re-use after connection establishment.
MLU VoIP join-in-progress (local user added to voip console-to-console connection after connection established) requires some
post-initial-handshaking reliability. For that purpose specifically, a reliability scheme was implemented with protocol components
piggy-backed on voip micr packets and voip ping packets. Those protocol components are: DATA, ACK and ACKCNF.
DATA (producer->consumer): data to be delivered reliably (tagged with a sequence number)
ACK (consumer->producer): acked sequence number
ACKCNF (producer->consumer): acked sequence number confirmation (can be leveraged by the consumer to stop “acking” and eliminate the associated “acking” overhead)
From the perspective of a game console producing reliable data, an "outbound reliable data flow" on a given connection looks like this:
Reliable data producer (local) Reliable Data Consumer (remote)
------ DATA ----->
<------ ACK -----
------ ACKCNF ----->
From the perspective of a game console consuming reliable data, an "inbound reliable data flow" on a given connection looks like this:
Reliable data consumer (local) Reliable Data Producer (remote)
<------ DATA -----
------ ACK ----->
<------ ACKCNF -----
ACK and ACKCNF protocol components are added, when necessary, as a pair to the beginning of any ping or voip packet. When the packet
is flagged with VOIP_PACKET_RELIABLE_FLAG_ACK_ACKCNF, then it contains at least one such entry. The exact number of entries is
captured in the first byte of the packet payload. Then each entry consists of a 4-byte destination client id and a 1-byte field where:
bit 7 = ACKCNF (1 means "continue acking", 0 means "you can stop acking")
bits 0-6 = ACK (acked seq number in the 1 to 127 range - 0 means "unused" or "invalid")
DATA protocol component is also added at the beginning of the voip packet payload (after ACK+ACkNF if any). When the packet
is flagged with VOIP_PACKET_RELIABLE_FLAG_DATA, then it contains at least one such entry. The exact number of entries is
captured in the first byte of the packet payload (or in the first byte following the ACK+ACKCNF portion). When a DATA entry
is created, it is queued with the SAME sequence number in the outbound queue of all connections. The use of the SAME
sequence number is key in not having to specifiy what destination console the entry is for. For P2P connectivity this is not
really a concern as packets are exchanged directly between consoles. But for voipserver-based connectivity, the voip micr
packets are sent once to the voipserver and then rebroadcasted from there to all other peers. Consequently it is important that
the sequence number that the DATA entries are tagged with are meaningful to all remote peers.
For voipserver-based voip:
Have a look at addition comments in _VoipReliableDataOutProcess() describing the logics used to add DATA entries
to voip micr packets sent to the voipserver.
\endverbatim
\Version 1.0 03/17/2004 (jbrookes) First Version
\Version 1.1 01/06/2008 (mclouatre) Created VoipConnectionAddRef(), VoipConnectionRemoveRef(), VoipConnectionResetRef(), VoipConnectionGetFallbackAddr()
\Version 1.2 10/26/2009 (mclouatre) Renamed from xbox/voipconnection.c to xenon/voipconnectionxenon.c
\Version 1.3 11/13/2009 (jbrookes) Merged Xenon back into common version
\Version 1.4 09/29/2014 (mclouatre) Addes support for voip reliable data
*/
/********************************************************************************H*/
/*** Include files ****************************************************************/
#ifdef _XBOX
#include <xtl.h>
#include <xonline.h>
#endif
#include <stdio.h>
#include <string.h>
#include <wchar.h> // for wcslen()
#include "DirtySDK/platform.h"
#include "DirtySDK/dirtysock.h"
#include "DirtySDK/dirtysock/dirtymem.h"
#include "DirtySDK/voip/voipdef.h"
#include "voippriv.h"
#include "voipcommon.h"
#include "voipconnection.h"
/*** Defines **********************************************************************/
#define VOIP_PACKET_VERSION ('j') //!< current packet version
#define VOIP_PING_RATE (500) //!< send ping packets twice a second
#define VOIP_TIMEOUT (15*1000) //!< default voip data timeout
#define VOIP_CONNTIMEOUT (10*1000) //!< connection timeout
#define VOIP_MSPERPACKET (100) //!< number of ms per network packet (100ms = 10hz)
//! enable for verbose debugging
#define VOIP_CONNECTION_DEBUG (DIRTYCODE_DEBUG && FALSE)
#define VOIP_RELIABLE_DEBUG (DIRTYCODE_DEBUG && FALSE)
#define VOIP_IPPROTO (IPPROTO_IP)
/*** Macros ***********************************************************************/
//! compare packet types
#define VOIP_SamePacketType(_packetHead1, _packetHead2) \
(!memcmp((_packetHead1)->aType, (_packetHead2)->aType, sizeof((_packetHead1)->aType)))
//! get connection ID
#define VOIP_ConnID(_pConnectionlist, _pConnection) ((uint32_t)((_pConnection)-(_pConnectionlist)->pConnections))
/*** Type Definitions *************************************************************/
#if DIRTYCODE_LOGGING
const char *_strReliableType[] =
{
"USERADD",
"USERREM",
"OPAQUE ",
"TEXT "
};
#endif
/*** Function Prototypes **********************************************************/
static void _VoipConnectionStop(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, int32_t iConnID, int32_t bSendDiscMsg);
static void _VoipEncodeU16(uint8_t *pValue, uint16_t uValue);
static uint16_t _VoipDecodeU16(const uint8_t *pValue);
/*** Variables ********************************************************************/
//! VoIP conn packet header
static VoipPacketHeadT _Voip_ConnPacket =
{
{ 'C', 'O', VOIP_PACKET_VERSION }, 0, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, {0, 0}
};
//! VoIP disc packet header
static VoipPacketHeadT _Voip_DiscPacket =
{
{ 'D', 'S', 'C' }, 0, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, {0, 0}
};
//! VoIP ping packet header
static VoipPacketHeadT _Voip_PingPacket =
{
{ 'P', 'N', 'G' }, 0, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, {0, 0}
};
//! VoIP mic data packet header
static VoipPacketHeadT _Voip_MicrPacket =
{
{ 'M', 'I', 'C' }, 0, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, {0, 0}
};
/*** Private Functions ************************************************************/
/*F********************************************************************************/
/*!
\Function _VoipEncodeLocalHeadsetStatus
\Description
Encode the uLocalUserStatus fields in the packet header
\Input *pConnectionlist - information read from here to be written to the packet header
\Input *pHead - packet header to write to
\Version 11/20/2008 (cvienneau)
*/
/********************************************************************************F*/
static void _VoipEncodeLocalHeadsetStatus(VoipConnectionlistT *pConnectionlist, VoipPacketHeadT *pHead)
{
uint16_t uFlags = 0;
int32_t i = 0;
for (i = 0; i < VOIP_MAXLOCALUSERS; ++i)
{
if (pConnectionlist->uLocalUserStatus[i] & VOIP_LOCAL_USER_HEADSETOK)
{
uFlags |= (1 << i);
}
}
// Encode 16 for byte ordering
_VoipEncodeU16(pHead->aHeadsetStat, uFlags);
}
/*F********************************************************************************/
/*!
\Function _VoipDecodeRemoteHeadsetStatus
\Description
Reads aHeadsetStat field from an incoming packet and sets appropriate state
about the remote connection.
\Input *pConnection - connection to write state to
\Input *pHead - packet header to read information from
\Version 11/20/2008 (cvienneau)
*/
/********************************************************************************F*/
static void _VoipDecodeRemoteHeadsetStatus(VoipConnectionT *pConnection, VoipPacketHeadT *pHead)
{
uint32_t iIndex;
uint16_t uFlags = _VoipDecodeU16(pHead->aHeadsetStat);
for (iIndex = 0; iIndex < VOIP_MAXLOCALUSERS; ++iIndex)
{
uint16_t uMask = 1 << iIndex;
if (uFlags & uMask)
{
pConnection->uRemoteUserStatus[iIndex] |= VOIP_REMOTE_USER_HEADSETOK; //on
}
else
{
pConnection->uRemoteUserStatus[iIndex] &= ~VOIP_REMOTE_USER_HEADSETOK; //off
}
}
}
/*F********************************************************************************/
/*!
\Function _VoipDecodeU64
\Description
Decode uint64_t from given packet structure.
\Input *pValue - network-order data field
\Output
uint64_t - decoded value
\Version 07/03/2019 (tcho)
*/
/********************************************************************************F*/
static uint64_t _VoipDecodeU64(const uint8_t *pValue)
{
uint64_t uValue = (uint64_t)pValue[0] << 56;
uValue |= (uint64_t)pValue[1] << 48;
uValue |= (uint64_t)pValue[2] << 40;
uValue |= (uint64_t)pValue[3] << 32;
uValue |= (uint64_t)pValue[4] << 24;
uValue |= (uint64_t)pValue[5] << 16;
uValue |= (uint64_t)pValue[6] << 8;
uValue |= (uint64_t)pValue[7];
return(uValue);
}
/*F********************************************************************************/
/*!
\Function _VoipDecodeU32
\Description
Decode uint32_t from given packet structure.
\Input *pValue - network-order data field
\Output
uint32_t - decoded value
\Version 06/02/2006 (jbrookes)
*/
/********************************************************************************F*/
static uint32_t _VoipDecodeU32(const uint8_t *pValue)
{
uint32_t uValue = pValue[0] << 24;
uValue |= pValue[1] << 16;
uValue |= pValue[2] << 8;
uValue |= pValue[3];
return(uValue);
}
/*F********************************************************************************/
/*!
\Function _VoipDecodeU16
\Description
Decode uint16_t from given packet structure.
\Input *pValue - network-order data field
\Output
uint16_t - decoded value
\Version 05/02/2014 (jbrookes)
*/
/********************************************************************************F*/
static uint16_t _VoipDecodeU16(const uint8_t *pValue)
{
uint16_t uValue = pValue[0] << 8;
uValue |= pValue[1];
return(uValue);
}
/*F********************************************************************************/
/*!
\Function _VoipDecodeVoipUser
\Description
Decode a voip user to a voip user packet
\Input *pUserPacket - input voip user packet to decode
\Input *pUser - voip user to storge the decoded result
\Version 07/03/2019 (tcho)
*/
/********************************************************************************F*/
static void _VoipDecodeVoipUser(VoipUserPacketT *pUserPacket, VoipUserT *pUser)
{
pUser->AccountInfo.iAccountId = (int64_t)_VoipDecodeU64(pUserPacket->aAccountId);
pUser->AccountInfo.iPersonaId = (int64_t)_VoipDecodeU64(pUserPacket->aPersonaId);
pUser->uFlags = _VoipDecodeU32(pUserPacket->aFlags);
pUser->ePlatform = _VoipDecodeU32(pUserPacket->aPlatform);
}
/*F********************************************************************************/
/*!
\Function _VoipEncodeU64
\Description
Encode given uint64_t in network order
\Input *pValue - storage for network-order u64
\Input uValue - u64 value to encode
\Version 07/03/2019 (tcho)
*/
/********************************************************************************F*/
static void _VoipEncodeU64(uint8_t *pValue, uint64_t uValue)
{
pValue[0] = (uint8_t)(uValue >> 56);
pValue[1] = (uint8_t)(uValue >> 48);
pValue[2] = (uint8_t)(uValue >> 40);
pValue[3] = (uint8_t)(uValue >> 32);
pValue[4] = (uint8_t)(uValue >> 24);
pValue[5] = (uint8_t)(uValue >> 16);
pValue[6] = (uint8_t)(uValue >> 8);
pValue[7] = (uint8_t)(uValue);
}
/*F********************************************************************************/
/*!
\Function _VoipEncodeU32
\Description
Encode given uint32_t in network order
\Input *pValue - storage for network-order u32
\Input uValue - u32 value to encode
\Version 06/02/2006 (jbrookes)
*/
/********************************************************************************F*/
static void _VoipEncodeU32(uint8_t *pValue, uint32_t uValue)
{
pValue[0] = (uint8_t)(uValue >> 24);
pValue[1] = (uint8_t)(uValue >> 16);
pValue[2] = (uint8_t)(uValue >> 8);
pValue[3] = (uint8_t)uValue;
}
/*F********************************************************************************/
/*!
\Function _VoipEncodeU16
\Description
Encode given uint16_t in network order
\Input *pValue - storage for network-order u32
\Input uValue - u16 value to encode
\Version 05/02/2014 (amakoukji)
*/
/********************************************************************************F*/
static void _VoipEncodeU16(uint8_t *pValue, uint16_t uValue)
{
pValue[0] = (uint8_t)(uValue >> 8);
pValue[1] = (uint8_t)uValue;
}
/*F********************************************************************************/
/*!
\Function _VoipEncodeVoipUser
\Description
Encodes a voip user to a voip user packet
\Input *pUserPacket - output voip user packet
\Input *pUser - user to encode
\Version 07/03/2019 (tcho)
*/
/********************************************************************************F*/
static void _VoipEncodeVoipUser(VoipUserPacketT *pUserPacket, VoipUserT *pUser)
{
_VoipEncodeU64(pUserPacket->aAccountId, (uint64_t) pUser->AccountInfo.iAccountId);
_VoipEncodeU64(pUserPacket->aPersonaId, (uint64_t) pUser->AccountInfo.iPersonaId);
_VoipEncodeU32(pUserPacket->aFlags, pUser->uFlags);
_VoipEncodeU32(pUserPacket->aPlatform, pUser->ePlatform);
}
/*F********************************************************************************/
/*!
\Function _VoipConnectionIncrementReliableSeqNb
\Description
Return the next seq nb following the specified seq nb.
Sequence number validity range: [1,127] (0 = invalid)
\Input uSeqNb - input sequence number
\Output
uint8_t - output sequence number
\Version 09/18/2014 (mclouatre)
*/
/********************************************************************************F*/
static uint8_t _VoipConnectionIncrementReliableSeqNb(uint8_t uSeqNb)
{
if (uSeqNb == 127)
{
return(1);
}
else
{
return(uSeqNb + 1);
}
}
/*F********************************************************************************/
/*!
\Function _VoipReliableDataEnqueue
\Description
Add a reliable data entry to the tail of the specified linked list.
\Input **pListHead - pointe to head of list
\Input *pNewEntry - entry to be added
\Version 09/14/2014 (mclouatre)
*/
/********************************************************************************F*/
static void _VoipReliableDataEnqueue(LinkedReliableDataT **pListHead, LinkedReliableDataT *pNewEntry)
{
if (*pListHead == NULL)
{
*pListHead = pNewEntry;
}
else
{
// find tail of list and append
LinkedReliableDataT *pCurrent = *pListHead;
while (pCurrent->pNext != NULL)
{
pCurrent = pCurrent->pNext;
}
pCurrent->pNext = pNewEntry;
}
pNewEntry->pNext = NULL;
}
/*F********************************************************************************/
/*!
\Function _VoipReliableDataDequeue
\Description
Remove a reliable data entry from the tail of the specified linked list.
\Input **pListHead - pointer to head of list
\Output
LinkedReliableDataT * - pointer to returned buffer; NULL if error
\Version 09/14/2014 (mclouatre)
*/
/********************************************************************************F*/
static LinkedReliableDataT *_VoipReliableDataDequeue(LinkedReliableDataT **pListHead)
{
LinkedReliableDataT *pRemovedEntry = NULL;
// return head of list to caller
if (*pListHead)
{
pRemovedEntry = *pListHead;
*pListHead = (*pListHead)->pNext;
pRemovedEntry->pNext = NULL;
}
return(pRemovedEntry);
}
/*F********************************************************************************/
/*!
\Function _VoipGetReliableDataBufferFromFreePool
\Description
Obtain a free reliable data buffer from the free pool.
(Extract from head of linked list)
\Input *pConnectionlist - connection list
\Input bAllocIfEmpty - enable/disable allocating buffer in pool is empty
\Output
LinkedReliableDataT * - pointer to returned buffer; NULL if error
\Version 09/14/2014 (mclouatre)
*/
/********************************************************************************F*/
static LinkedReliableDataT *_VoipGetReliableDataBufferFromFreePool(VoipConnectionlistT *pConnectionlist, uint8_t bAllocIfEmpty)
{
LinkedReliableDataT *pReliableDataBuffer;
int32_t iMemGroup;
void *pMemGroupUserData;
// query current mem group data
VoipCommonMemGroupQuery(&iMemGroup, &pMemGroupUserData);
// get a buffer from the pool of free buffer
pReliableDataBuffer = _VoipReliableDataDequeue(&pConnectionlist->pFreeReliableDataPool);
// if pool is empty, allocate a new entry
if ((pReliableDataBuffer == NULL) && (bAllocIfEmpty != FALSE))
{
if ((pReliableDataBuffer = (LinkedReliableDataT *)DirtyMemAlloc(sizeof(*pReliableDataBuffer), VOIP_MEMID, iMemGroup, pMemGroupUserData)) != NULL)
{
ds_memclr(pReliableDataBuffer, sizeof(*pReliableDataBuffer));
#if VOIP_RELIABLE_DEBUG
NetPrintf(("voipconnection: new free reliable data buffer (%p) added to free pool\n", pReliableDataBuffer));
#endif
}
else
{
NetPrintf(("voipconnection: error, unable to allocate reliable data buffer\n"));
}
}
#if VOIP_RELIABLE_DEBUG
if (pReliableDataBuffer)
{
NetPrintf(("voipconnection: reliable data buffer (%p) obtained from free pool\n", pReliableDataBuffer));
}
else
{
NetPrintf(("voipconnection: free pool of reliable data buffers is now empty\n"));
}
#endif
return(pReliableDataBuffer);
}
/*F********************************************************************************/
/*!
\Function _VoipReturnReliableDataBufferToFreePool
\Description
Return buffer to free pool. (Append to tail of linked list)
\Input *pConnectionlist - connection list
\Input *pFreeBuffer - buffer to be returned to pool
\Version 09/14/2014 (mclouatre)
*/
/********************************************************************************F*/
static void _VoipReturnReliableDataBufferToFreePool(VoipConnectionlistT *pConnectionlist, LinkedReliableDataT *pFreeBuffer)
{
_VoipReliableDataEnqueue(&pConnectionlist->pFreeReliableDataPool, pFreeBuffer);
#if VOIP_RELIABLE_DEBUG
NetPrintf(("voipconnection: reliable data buffer (%p) returned to free pool\n", pFreeBuffer));
#endif
}
/*F********************************************************************************/
/*!
\Function _VoipPacketGetWritePtr
\Description
Calculate next write position in the specified voip packet
\Input *pMicrPacket - packet pointer
\Output
uint8_t * - next write position in the packet
\Version 07/18/2013 (mclouatre)
*/
/********************************************************************************F*/
static uint8_t *_VoipPacketGetWritePtr(VoipMicrPacketT *pMicrPacket)
{
uint8_t *pWrite = &pMicrPacket->aData[0];
// is the packet buffer already filled with ACK+ACKCNF protocol components used for the reliability mechanism?
if (pMicrPacket->Head.uFlags & VOIP_PACKET_RELIABLE_FLAG_ACK_ACKCNF)
{
int32_t iReliableAckEntriesCount = *pWrite++;
pWrite += (iReliableAckEntriesCount * sizeof(ReliableAckT));
}
// is the packet buffer already filled with the DATA protocol component used for the reliability mechanism?
if (pMicrPacket->Head.uFlags & VOIP_PACKET_RELIABLE_FLAG_DATA)
{
int32_t iReliableDataEntriesCount = *pWrite++;
int32_t iReliableDataEntryIndex;
for (iReliableDataEntryIndex = 0; iReliableDataEntryIndex < iReliableDataEntriesCount; iReliableDataEntryIndex++)
{
ReliableDataT *pCurrent = (ReliableDataT *)pWrite;
uint32_t uSize = _VoipDecodeU16(&pCurrent->info.uSize[0]);
pWrite += sizeof(pCurrent->info) + uSize;
}
}
// is the packet buffer already filled with some metadata?
if (pMicrPacket->Head.uFlags & VOIP_PACKET_STATUS_FLAG_METADATA)
{
int32_t iMetaDataSize;
iMetaDataSize = *pWrite++;
pWrite += iMetaDataSize;
}
// is the packet buffer already filled with some sub-pkts?
if (pMicrPacket->MicrInfo.uNumSubPackets != 0)
{
uint8_t bVariableSubPktLen = ((pMicrPacket->MicrInfo.uSubPacketSize == 0xFF) ? TRUE : FALSE); // check if sub-pkts in this packet are fixed length or variable length
// if necessary, jump over sub-pkts already packed in the packet buffer
if (bVariableSubPktLen)
{
int32_t iSubPktIndex, iSubPacketSize;
// find where we need to write; each sub-packet is prepended (one byte) with its length
for (iSubPktIndex = 0; iSubPktIndex < pMicrPacket->MicrInfo.uNumSubPackets; iSubPktIndex++)
{
iSubPacketSize = *pWrite;
pWrite += (iSubPacketSize + 1);
}
}
else
{
pWrite += (pMicrPacket->MicrInfo.uNumSubPackets * pMicrPacket->MicrInfo.uSubPacketSize);
}
}
return(pWrite);
}
/*F********************************************************************************/
/*!
\Function _VoipPacketGetMetaDataPtr
\Description
Find position of metadata in the specified voip packet
\Input *pMicrPacket - packet pointer
\Output
uint8_t * - ptr to metadata
\Version 09/25/2014 (mclouatre)
*/
/********************************************************************************F*/
static uint8_t *_VoipPacketGetMetaDataPtr(VoipMicrPacketT *pMicrPacket)
{
uint8_t *pMetaData = &pMicrPacket->aData[0];
// is the packet buffer already filled with ACK+ACKCNF protocol components used for the reliability mechanism?
if (pMicrPacket->Head.uFlags & VOIP_PACKET_RELIABLE_FLAG_ACK_ACKCNF)
{
int32_t iReliableAckEntriesCount = *pMetaData++;
pMetaData += (iReliableAckEntriesCount * sizeof(ReliableAckT));
}
// is the packet buffer already filled with the DATA protocol component used for the reliability mechanism?
if (pMicrPacket->Head.uFlags & VOIP_PACKET_RELIABLE_FLAG_DATA)
{
int32_t iReliableDataEntriesCount = *pMetaData++;
int32_t iReliableDataEntryIndex;
for (iReliableDataEntryIndex = 0; iReliableDataEntryIndex < iReliableDataEntriesCount; iReliableDataEntryIndex++)
{
ReliableDataT *pCurrent = (ReliableDataT *)pMetaData;
uint32_t uSize = _VoipDecodeU16(&pCurrent->info.uSize[0]);
pMetaData += sizeof(pCurrent->info) + uSize;
}
}
return(pMetaData);
}
/*F********************************************************************************/
/*!
\Function _VoipPrepareVoipServerSendMask
\Description
Convert local send mask to voip-server ready send mask
\Input *pConnectionlist - connection list
\Output
uint32_t - send mask to be packed in voip packet sent to voip server
\Version 10/27/2006 (mclouatre)
*/
/********************************************************************************F*/
static uint32_t _VoipPrepareVoipServerSendMask(VoipConnectionlistT *pConnectionlist)
{
int32_t iLocalConnId, iVoipServerConnId;
uint32_t uLocalSendMask = pConnectionlist->uSendMask;
uint32_t uVoipServerSendMask = 0;
for (iLocalConnId = 0; iLocalConnId < pConnectionlist->iMaxConnections; iLocalConnId++)
{
iVoipServerConnId = pConnectionlist->pConnections[iLocalConnId].iVoipServerConnId;
// make sure connection is used by voip server
if (iVoipServerConnId != VOIP_CONNID_NONE)
{
// make sure local connection has send bit set
if (uLocalSendMask & (1 << iLocalConnId))
{
// set the proper bit in the send mask for the voip server
uVoipServerSendMask |= (1 << iVoipServerConnId);
}
}
}
return(uVoipServerSendMask);
}
/*F********************************************************************************/
/*!
\Function _VoipConnectionIsTranscribedTextRequested
\Description
Returns TRUE if at least one local user is requesting text
transcription from remote talkers.
\Input *pConnectionlist - pointer to connectionlist
\Output
uint32_t - TRUE or FALSE
\Version 05/05/2017 (mclouatre)
*/
/********************************************************************************F*/
static uint32_t _VoipConnectionIsTranscribedTextRequested(VoipConnectionlistT *pConnectionlist)
{
int32_t iUserIndex;
for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS; iUserIndex++)
{
if (pConnectionlist->aIsParticipating[iUserIndex] != FALSE)
{
if (pConnectionlist->bTranscribedTextRequested[iUserIndex] != FALSE)
{
return(TRUE);
}
}
}
return(FALSE);
}
/*F********************************************************************************/
/*!
\Function _VoipConnectionAllocate
\Description
Allocate an empty connection for use.
\Input *pConnectionlist - pointer to connectionlist
\Input iConnID - connection to allocate (or VOIP_CONNID_*)
\Output
VoipConnectionT * - pointer to connection to use or NULL
\Version 03/17/2004 (jbrookes)
*/
/********************************************************************************F*/
static VoipConnectionT *_VoipConnectionAllocate(VoipConnectionlistT *pConnectionlist, int32_t iConnID)
{
VoipConnectionT *pConnection = NULL;
// get a connection ID
pConnection = NULL;
if (iConnID == VOIP_CONNID_NONE)
{
// find a free connection slot
for (iConnID = 0; iConnID < pConnectionlist->iMaxConnections; iConnID++)
{
// if this is an unallocated connection
if (pConnectionlist->pConnections[iConnID].eState == ST_DISC)
{
pConnection = &pConnectionlist->pConnections[iConnID];
break;
}
}
// make sure we found a connection
if (pConnection == NULL)
{
NetPrintf(("voipconnection: out of connections\n"));
}
}
else if ((iConnID >= 0) && (iConnID < pConnectionlist->iMaxConnections))
{
// if we're currently connected, complain
if (pConnectionlist->pConnections[iConnID].eState != ST_DISC)
{
NetPrintf(("voipconnection: [%d] connection not available, currently used for clientId=%d\n", iConnID, pConnectionlist->pConnections[iConnID].uRemoteClientId));
return(NULL);
}
// ref connection
pConnection = &pConnectionlist->pConnections[iConnID];
}
else
{
NetPrintf(("voipconnection: %d is an invalid connection id\n", iConnID));
}
// return connection ref to caller
return(pConnection);
}
/*F********************************************************************************/
/*!
\Function _VoipSocketSendto
\Description
Send data with the given socket.
\Input *pSocket - socket to send on
\Input uClientId - client identifier
\Input *pBuf - data to send
\Input iLen - size of data to send
\Input iFlags - send flags
\Input *pTo - address to send data to
\Input iToLen - length of address structure
\Output
int32_t - amount of data sent
\Version 11/30/2005 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _VoipSocketSendto(SocketT *pSocket, uint32_t uClientId, VoipPacketHeadT *pBuf, int32_t iLen, int32_t iFlags, struct sockaddr *pTo, int32_t iToLen)
{
// make sure socket exists
if (pSocket == NULL)
{
return(0);
}
// set connection identifier (goes in all packets)
_VoipEncodeU32(pBuf->aClientId, uClientId);
// send the packet
return(SocketSendto(pSocket, (char *)pBuf, iLen, iFlags, pTo, iToLen));
}
/*F********************************************************************************/
/*!
\Function _VoipConnectionMatchSessionId
\Description
Check whether specified session ID matchs the voip connection.
\Input *pConnectionlist - connectionlist
\Input iConnId - connection ID
\Input uSessionId - session ID
\Output
uint8_t - TRUE for match, FALSE for no match
\Version 08/30/2011 (mclouatre)
*/
/********************************************************************************F*/
static uint8_t _VoipConnectionMatchSessionId(VoipConnectionlistT *pConnectionlist, int32_t iConnId, uint32_t uSessionId)
{
int32_t iSessionIndex;
int32_t bMatch = FALSE;
VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnId];
NetCritEnter(&pConnectionlist->NetCrit);
for (iSessionIndex = 0; iSessionIndex < VOIP_MAXSESSIONIDS; iSessionIndex++)
{
if (pConnection->uSessionId[iSessionIndex] == uSessionId)
{
bMatch = TRUE;
}
}
NetCritLeave(&pConnectionlist->NetCrit);
return(bMatch);
}
#if DIRTYCODE_LOGGING
/*F********************************************************************************/
/*!
\Function _VoipConnectionPrintRemoteUsers
\Description
Print remote users
\Input *pConnection - connection to transition
\Input iConnID - index of connection
\Version 03/11/2014 (mclouatre)
*/
/********************************************************************************F*/
static void _VoipConnectionPrintRemoteUsers(VoipConnectionT *pConnection, int32_t iConnID)
{
int32_t iIndex;
for (iIndex = 0; iIndex < pConnection->iMaxRemoteUsers; iIndex++)
{
if (!VOIP_NullUser((VoipUserT *)(&pConnection->RemoteUsers[iIndex])))
{
NetPrintf(("voipconnection: [%d] remote user #%d --> %lld\n", iConnID, iIndex, ((VoipUserT *)&pConnection->RemoteUsers[iIndex])->AccountInfo.iPersonaId));
}
}
}
/*F********************************************************************************/
/*!
\Function _VoipConnectionPrintSessionIds
\Description
Prints all session IDs sharing this voip connection
\Input *pConnectionlist - connectionlist
\Input iConnId - connection id
\Version 08/30/2011 (mclouatre)
*/
/********************************************************************************F*/
static void _VoipConnectionPrintSessionIds(VoipConnectionlistT *pConnectionlist, int32_t iConnId)
{
int32_t iSessionIndex;
VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnId];
NetCritEnter(&pConnectionlist->NetCrit);
if (pConnection->uSessionId[0] != 0)
{
NetPrintf(("voipconnection: [%d] IDs of sessions sharing this voip connection are: ", iConnId));
for (iSessionIndex = 0; iSessionIndex < VOIP_MAXSESSIONIDS; iSessionIndex++)
{
if (pConnection->uSessionId[iSessionIndex] != 0)
{
NetPrintf(("0x%08x\n", pConnection->uSessionId[iSessionIndex]));
}
else
{
break;
}
}
}
else
{
NetPrintf(("voipconnection: [%d] there is no session ID associated with this connection\n", iConnId));
}
NetCritLeave(&pConnectionlist->NetCrit);
}
#endif // DIRTYCODE_LOGGING
/*F********************************************************************************/
/*!
\Function _VoipConnectionReliableDataEnqueue
\Description
Add a reliable data entry to the outbound queue of the
specified connection. (Append to tail of linked list)
\Input *pConnectionlist - connection list
\Input iConnID - connection identifier (VOIP_CONNID_ALL for all)
\Input *pReliableData - pointer to reliable data to be enqueued
\Version 09/14/2014 (mclouatre)
*/
/********************************************************************************F*/
static void _VoipConnectionReliableDataEnqueue(VoipConnectionlistT *pConnectionlist, int32_t iConnID, ReliableDataT *pReliableData)
{
int32_t iConnectionIndex;
uint32_t uCurrentTick = NetTick();
if (iConnID == VOIP_CONNID_ALL)
{
iConnectionIndex = 0;
}
else
{
iConnectionIndex = iConnID;
}
while (iConnectionIndex < pConnectionlist->iMaxConnections)
{
VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnectionIndex];
if (pConnection->eState == ST_ACTV || pConnection->eState == ST_CONN)
{
LinkedReliableDataT *pNewEntry = _VoipGetReliableDataBufferFromFreePool(pConnectionlist, TRUE);
if (pNewEntry)
{
// fill with input data
ds_memcpy_s(&pNewEntry->data, sizeof(pNewEntry->data), pReliableData, sizeof(*pReliableData));
// fill sequence number
pNewEntry->data.info.uSeq = pConnection->uOutSeqNb;
pConnection->uOutSeqNb = _VoipConnectionIncrementReliableSeqNb(pConnection->uOutSeqNb);
// set remote peer id
_VoipEncodeU32(&pNewEntry->data.info.aRemoteClientId[0], pConnection->uRemoteClientId);
// fill enqueue tick
pNewEntry->uEnqueueTick = uCurrentTick;
// add to oubtound queue
_VoipReliableDataEnqueue(&pConnection->pOutboundReliableDataQueue, pNewEntry);
#if VOIP_RELIABLE_DEBUG
NetPrintf(("voipconnection: [%d] reliable data buffer (%p) added to outbound queue (seq# %03d, %s, tick=%u)\n", iConnectionIndex, pNewEntry,
pNewEntry->data.info.uSeq, _strReliableType[pNewEntry->data.info.uType], pNewEntry->uEnqueueTick));
#endif
/*
In the case of voipserver-based topology, to improve responsiveness and increase chances of reliable data
making it quick to other end, we alter the last send tick on the connection. This should result in a
ping packets being sent immediately, and help with reliable data reaching other end for voip connections
that see their voip traffic squelched by the voip server.
*/
if (pConnection->iVoipServerConnId != VOIP_CONNID_NONE)
{
#if VOIP_RELIABLE_DEBUG
NetPrintf(("voipconnection: [%d] last send tick altered to speed up sending of reliable data via ping packet\n", iConnectionIndex));
#endif
pConnection->uLastSend = pConnection->uLastSend - VOIP_PING_RATE;
}
}
else
{
NetPrintf(("voipconnection: [%d] failed to obtain resources for enqueueing new outbound reliable data\n", iConnectionIndex));
}
}
// break out of while loop if we are dealing with a single connection only
if (iConnID == VOIP_CONNID_ALL)
{
iConnectionIndex++;
}
else
{
break;
}
}
}
/*F********************************************************************************/
/*!
\Function _VoipConnectionReliableDataDequeue
\Description
Remove reliable data entry from the outbound queue of the
specified connection. (Remove entry at head of linked list)
\Input *pConnectionlist - connection list
\Input iConnID - connection identifier (VOIP_CONNID_ALL for all)
\Version 09/14/2014 (mclouatre)
*/
/********************************************************************************F*/
static void _VoipConnectionReliableDataDequeue(VoipConnectionlistT *pConnectionlist, int32_t iConnID)
{
LinkedReliableDataT *pRemovedEntry = _VoipReliableDataDequeue(&pConnectionlist->pConnections[iConnID].pOutboundReliableDataQueue);
#if VOIP_RELIABLE_DEBUG
NetPrintf(("voipconnection: [%d] reliable data buffer (%p) removed from outbound queue (seq# %03d, %s)\n", iConnID, pRemovedEntry,
pRemovedEntry->data.info.uSeq, _strReliableType[pRemovedEntry->data.info.uType]));
#endif
_VoipReturnReliableDataBufferToFreePool(pConnectionlist, pRemovedEntry);
}
/*F********************************************************************************/
/*!
\Function _VoipReliableDataPackACKandACKCNF
\Description
Outbound processing of reliablity data - deals with ACK+ACKCNF protocol
components specifically.
\Input *pConnectionlist - connectionlist ref
\Input iConnID - id of connection to be acked; VOIP_CONNID_ALL to ack all connections
\Input *pFlags - pointer to uFlags field of the outbound packet
\Input bPing - TRUE if packet is a ping packet, FALSE if packet is a micr packet
\Input *pWrite - pointer to next byte to be written in packet payload
\Input iSpaceLeftInPkt - amount of space left in packet payload
\Output
uint8_t * - pointer to first byte after the newly added ACK
\Version 09/14/2014 (mclouatre)
*/
/********************************************************************************F*/
static uint8_t * _VoipReliableDataPackACKandACKCNF(VoipConnectionlistT *pConnectionlist, int32_t iConnID, uint8_t *pFlags, uint32_t bPing, uint8_t *pWrite, int32_t iSpaceLeftInPkt)
{
uint8_t uEntryCount = 0;
uint8_t *pEntryCount = pWrite; // remember where the entry count needs to be written if any entry added to packet buffer
uint32_t uSpaceLeftInPkt = (uint32_t)iSpaceLeftInPkt; // amount of space still available in packet for reliable data
int32_t iConnectionIndex;
// skip the byte where the entry count is supposed to be written
pWrite++;
if (iConnID == VOIP_CONNID_ALL)
{
iConnectionIndex = 0;
}
else
{
iConnectionIndex = iConnID;
}
while (iConnectionIndex < pConnectionlist->iMaxConnections)
{
VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnectionIndex];
uint32_t bAckOrAckCnfNeeded = FALSE;
// if our outbound queue is not empty or if the peer at the remote end of the connection is still acking us, then
// add ACK+ACKCNF protocol component to packet.
if ((pConnectionlist->pConnections[iConnectionIndex].pOutboundReliableDataQueue != NULL) || pConnection->bPeerIsAcking)
{
bAckOrAckCnfNeeded = TRUE;
}
// make sure there is enough space left in destination packet buffer
if (sizeof(ReliableAckT) < uSpaceLeftInPkt)
{
// do we need to send ACK or ACKCNF to this remote client
if (pConnection->bPeerNeedsAck || bAckOrAckCnfNeeded)
{
ReliableAckT ackEntry;
// set remote peer id
_VoipEncodeU32(&ackEntry.aRemoteClientId[0], pConnection->uRemoteClientId);
// Fill in ACK value
if (pConnection->bPeerNeedsAck)
{
/* validty range: [1,127]
In a scenario where we have no yet received inbound reliable traffic from remote peer, pConnection->uInSeqNb
is 0 and we end up initializing ackEntry.uAckedSeq with 0 which means 'ignore this field, no sequence number acked' */
ackEntry.uAckedSeq = pConnection->uInSeqNb;
}
else
{
// means: 'ignore this field, no sequence number acked'
ackEntry.uAckedSeq = 0;
}
// set ACKCNF
if (bAckOrAckCnfNeeded)
{
// if our outbound queue is not empty, flag that we are still expecting acks back
if (pConnection->pOutboundReliableDataQueue != NULL)
{
ackEntry.uAckedSeq |= VOIP_RELIABLE_ACKCNF_MASK;
}
}
// add entry in the packet
ds_memcpy_s(pWrite, uSpaceLeftInPkt, &ackEntry, sizeof(ackEntry));
pWrite += sizeof(ackEntry);
// removed space used from space left
uSpaceLeftInPkt -= sizeof(ackEntry);
// increment entry count
uEntryCount += 1;
// flag that packet contains at least one ACK+ACKCNF protocol component entry
*pFlags |= VOIP_PACKET_RELIABLE_FLAG_ACK_ACKCNF;
#if VOIP_RELIABLE_DEBUG
if (pConnection->bPeerNeedsAck)
{
NetPrintf(("voipconnection: [%d] reliable ACK+ACKCNF (acked seq# %03d, %s) added to outbound %s packet for clientId=0x%08x\n", iConnectionIndex,
(ackEntry.uAckedSeq & 0x7F), ((pConnection->pOutboundReliableDataQueue != NULL)?"SendAck":"HaltAck"), (bPing?"PNG":"MIC"), pConnection->uRemoteClientId));
}
else
{
NetPrintf(("voipconnection: [%d] reliable ACK+ACKCNF (acked seq# INV, %s) added to outbound %s packet for clientId=0x%08x\n", iConnectionIndex,
((pConnection->pOutboundReliableDataQueue != NULL) ?"SendAck":"HaltAck"), (bPing?"PNG":"MIC"), pConnection->uRemoteClientId));
}
#endif
}
}
else
{
NetPrintf(("voipconnection: warning - failed to fit all ACK entries in outbound %s packet\n", (bPing?"PNG":"MIC")));
break; // no space left in packet, force while loop to exit
}
// break out of while loop if we are dealing with a single connection only
if (iConnID == VOIP_CONNID_ALL)
{
iConnectionIndex++;
}
else
{
break;
}
}
if (uEntryCount == 0)
{
// reposition pWrite to its initial value
pWrite = pEntryCount;
}
else
{
// write the entry count in the first byte that the input pWrite was pointing to
*pEntryCount = uEntryCount;
}
return(pWrite);
}
/*F********************************************************************************/
/*!
\Function _VoipReliableDataPackDATA
\Description
Outbound processing of reliablity data - deals with DATA protocol component specifically.
\Input *pConnectionlist - connectionlist ref
\Input iConnID - id of connection on which the outbound packet is being sent; CANNOT be VOIP_CONNID_ALL
\Input *pFlags - pointer to uFlags field of the outbound packet
\Input bPing - TRUE if packet is a ping packet, FALSE if packet is a micr packet
\Input *pWrite - pointer to next byte to be written in packet payload
\Input iSpaceLeftInPkt - amount of space left in packet payload
\Output
uint8_t * - pointer to first byte after the newly added DATA
\Version 09/14/2014 (mclouatre)
*/
/********************************************************************************F*/
static uint8_t * _VoipReliableDataPackDATA(VoipConnectionlistT *pConnectionlist, int32_t iConnID, uint8_t *pFlags, uint32_t bPing, uint8_t *pWrite, int32_t iSpaceLeftInPkt)
{
uint8_t uEntryCount = 0;
uint8_t *pEntryCount = pWrite; // remember where the entry count needs to be written if any entry added to packet buffer
uint32_t uSpaceLeftInPkt = (uint32_t)iSpaceLeftInPkt; // amount of space still available in packet for reliable data
VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnID];
LinkedReliableDataT *pCurrent = pConnection->pOutboundReliableDataQueue;
// skip the byte where the entry count is supposed to be written
pWrite++;
// find how many entries from the outbound reliable data queue fit in the outbound packet
while (pCurrent != NULL)
{
uint16_t uSize = _VoipDecodeU16(&pCurrent->data.info.uSize[0]);
if (uSize < uSpaceLeftInPkt)
{
// pack reliable data info
ds_memcpy(pWrite, &pCurrent->data.info, sizeof(pCurrent->data.info));
pWrite += sizeof(pCurrent->data.info);
// pack reliable data payload
ds_memcpy(pWrite, pCurrent->data.aData, uSize);
pWrite += uSize;
// removed space used from space left
uSpaceLeftInPkt -= uSize;
// increment the entry count in the packet
uEntryCount += 1;
// flag that packet contains at least one DATA protocol component entry
*pFlags |= VOIP_PACKET_RELIABLE_FLAG_DATA;
#if VOIP_RELIABLE_DEBUG
{
int32_t uClientId = _VoipDecodeU32(&pCurrent->data.info.aRemoteClientId[0]);
NetPrintf(("voipconnection: [%d] reliable DATA (target: 0x%08x, seq# %03d, %s, %d bytes) added to outbound %s packet\n", iConnID, uClientId, pCurrent->data.info.uSeq,
_strReliableType[pCurrent->data.info.uType], uSize, (bPing?"PNG":"MIC")));
}
#endif
// move to next pending entry
pCurrent = pCurrent->pNext;
}
else
{
NetPrintf(("voipconnection: [%d] warning - failed to fit all DATA entries in outbound %s packet (last entry size = %d)\n",
iConnID, (bPing?"PNG":"MIC"), uSize));
pCurrent = NULL; // no space left in packet, force while loop to exit
}
}
if (uEntryCount == 0)
{
// reposition pWrite to its initial value
pWrite = pEntryCount;
}
else
{
// write the entry count in the first byte that the input pWrite was pointing to
*pEntryCount = uEntryCount;
}
return(pWrite);
}
/*F********************************************************************************/
/*!
\Function _VoipReliableDataOutProcess
\Description
Outbound processing of reliablity data. To be invoked every time a
ping packet or a micr packet is sent. Reliability protocol components
(ACK, ACKCNF, DATA) are going to be added to the packet if necessary.
\Input *pConnectionlist - connectionlist ref
\Input iConnID - connection id; VOIP_CONNID_ALL if packet is being sent to voipserver
\Input *pPacketHeader - pointer to header of the outbound packet
\Input *pPacketPayload - pointer to first payload byte of outbound packet
\Input iPayloadCapacity - amount of bytes we can fit in the payload
\Output
uint8_t * - pointer to first byte after reliable data in packet payload
\Version 09/14/2014 (mclouatre)
*/
/********************************************************************************F*/
static uint8_t * _VoipReliableDataOutProcess(VoipConnectionlistT *pConnectionlist, int32_t iConnID, VoipPacketHeadT *pPacketHeader, uint8_t *pPacketPayload, int32_t iPayloadCapacity)
{
int32_t iSpaceLeftInPkt = iPayloadCapacity;
uint32_t bPing = ((pPacketHeader->aType[0] == 'P' && pPacketHeader->aType[1] == 'N' && pPacketHeader->aType[2] == 'G') ? TRUE : FALSE);
uint8_t *pWrite;
// add ACK and ACKCNF to packet as necessary
pWrite = _VoipReliableDataPackACKandACKCNF(pConnectionlist, iConnID, &pPacketHeader->uFlags, bPing, pPacketPayload, iSpaceLeftInPkt);
iSpaceLeftInPkt -= (pWrite - pPacketPayload);
if (iConnID == VOIP_CONNID_ALL)
{
int32_t iConnectionIndex;
int32_t iLargestTickDiff = 0;
/*
When voip traffic flows through the voip server, we need to pick a local voip connection
to pack the outbound reliable data entries from. Each connection have its own outbound
reliable queue. They are all populated with same entries tagged with same sequence numbers.
But the acking progress on each queue may progress differently. For instance, queue X may
have entry seq nb 10 acked, but queue Y may still be waiting for entry seq nb 8 to be acked.
The best we can do is pick the connection that has the oldest pending reliable data. Other
connections will benefit of this too if there are still waiting for ack of that entry, or
they will rely on the next ping packet to go out.
*/
for (iConnectionIndex = 0; iConnectionIndex < pConnectionlist->iMaxConnections; iConnectionIndex++)
{
VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnectionIndex];
if ((pConnection->eState == ST_ACTV) && (pConnection->pOutboundReliableDataQueue != NULL))
{
int32_t iTickDiff = NetTickDiff(NetTick(), pConnection->pOutboundReliableDataQueue->uEnqueueTick);
if (iLargestTickDiff < iTickDiff)
{
iLargestTickDiff = iTickDiff;
iConnID = iConnectionIndex;
}
}
}
// if above for loop did not match on any connection, fallback to connection index 0
if (iConnID == VOIP_CONNID_ALL)
{
iConnID = 0;
}
}
pWrite = _VoipReliableDataPackDATA(pConnectionlist, iConnID, &pPacketHeader->uFlags, bPing, pWrite, iSpaceLeftInPkt);
return(pWrite);
}
/*F********************************************************************************/
/*!
\Function _VoipReliableDataInboundUserAddProcess
\Description
Inbound processing of reliablity data of type VOIP_RELIABLE_TYPE_USERADD.
\Input *pConnectionlist - connectionlist ref
\Input *pConnection - connection on which the packet was received
\Input *pReliableData - pointer to inbound reliable data entry
\Version 09/14/2014 (mclouatre)
*/
/********************************************************************************F*/
static void _VoipReliableDataInboundUserAddProcess(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, ReliableDataT *pReliableData)
{
uint8_t uUserIndex = pReliableData->aData[0]; // first byte is user index
VoipUserT *pRemoteUser = (VoipUserT *)(&pReliableData->aData[0] + 1);
if (VOIP_NullUser((VoipUserT *)&pConnection->RemoteUsers[uUserIndex]))
{
int32_t iConnID = pConnection - &pConnectionlist->pConnections[0];
// update collection of remote users for this connection
VOIP_CopyUser(&pConnection->RemoteUsers[uUserIndex], pRemoteUser);
// register the user
pConnectionlist->pRegUserCb((VoipUserT *)&pConnection->RemoteUsers[uUserIndex], (uint32_t)iConnID, TRUE, pConnectionlist->pRegUserUserData);
// force re-application of channel config since we have a new remote player
((VoipCommonRefT *)VoipGetRef())->bApplyChannelConfig = TRUE;
#if defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK)
// reapply player-to-player xone comm relationships
pConnectionlist->bApplyRelFromMuting = TRUE;
#endif
// set mute list to be updated
pConnectionlist->bUpdateMute = TRUE;
#if DIRTYCODE_LOGGING
_VoipConnectionPrintRemoteUsers(pConnection, (pConnection - &pConnectionlist->pConnections[0]));
#endif
}
else
{
NetPrintf(("voipconnection: [%d] join-in-progress for remote user %lld failed because index %d already used by %lld\n", (pConnection - &pConnectionlist->pConnections[0]),
((VoipUserT *)pRemoteUser)->AccountInfo.iPersonaId, uUserIndex, ((VoipUserT *)&pConnection->RemoteUsers[uUserIndex])->AccountInfo.iPersonaId));
}
}
/*F********************************************************************************/
/*!
\Function _VoipReliableDataInboundUserRemProcess
\Description
Inbound processing of reliablity data of type VOIP_RELIABLE_TYPE_USERREM.
\Input *pConnectionlist - connectionlist ref
\Input *pConnection - connection on which the packet was received
\Input *pReliableData - pointer to inbound reliable data entry
\Version 09/14/2014 (mclouatre)
*/
/********************************************************************************F*/
static void _VoipReliableDataInboundUserRemProcess(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, ReliableDataT *pReliableData)
{
uint8_t uUserIndex = pReliableData->aData[0]; // first byte is user index
VoipUserT *pRemoteUser = (VoipUserT *)(&pReliableData->aData[0] + 1);
if (!VOIP_NullUser((VoipUserT *)&pConnection->RemoteUsers[uUserIndex]))
{
if (memcmp(pRemoteUser, &pConnection->RemoteUsers[uUserIndex], sizeof(*pRemoteUser)) == 0)
{
int32_t iConnID = pConnection - &pConnectionlist->pConnections[0];
// unregister the user
pConnectionlist->pRegUserCb((VoipUserT *)&pConnection->RemoteUsers[uUserIndex], (uint32_t)iConnID, FALSE, pConnectionlist->pRegUserUserData);
// force re-application of channel config since we lost a remote player
((VoipCommonRefT *)VoipGetRef())->bApplyChannelConfig = TRUE;
#if defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK)
// reapply player-to-player xone comm relationships
pConnectionlist->bApplyRelFromMuting = TRUE;
#endif
// set mute list to be updated
pConnectionlist->bUpdateMute = TRUE;
// clear the user
VOIP_ClearUser((VoipUserT *)&pConnection->RemoteUsers[uUserIndex]);
#if DIRTYCODE_LOGGING
_VoipConnectionPrintRemoteUsers(pConnection, (pConnection - &pConnectionlist->pConnections[0]));
#endif
}
else
{
NetPrintf(("voipconnection: [%d] leave-in-progress for remote user %lld failed because remote user at index %d rather is %lld\n", (pConnection - &pConnectionlist->pConnections[0]),
((VoipUserT *)pRemoteUser)->AccountInfo.iPersonaId, uUserIndex, ((VoipUserT *)&pConnection->RemoteUsers[uUserIndex])->AccountInfo.iPersonaId));
}
}
else
{
NetPrintf(("voipconnection: [%d] leave-in-progress for remote user %lld failed because no remote user at index %d\n", (pConnection - &pConnectionlist->pConnections[0]),
((VoipUserT *)pRemoteUser)->AccountInfo.iPersonaId, uUserIndex));
}
}
/*F********************************************************************************/
/*!
\Function _VoipReliableDataInboundOpaqueProcess
\Description
Inbound processing of reliablity data of type VOIP_RELIABLE_TYPE_OPAQUE.
\Input *pConnectionlist - connectionlist ref
\Input *pConnection - connection on which the packet was received
\Input *pReliableData - pointer to inbound reliable data entry
\Version 09/14/2014 (mclouatre)
*/
/********************************************************************************F*/
static void _VoipReliableDataInboundOpaqueProcess(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, ReliableDataT *pReliableData)
{
uint16_t uDataSize = _VoipDecodeU16(&pReliableData->info.uSize[0]);
int32_t iConnID = pConnection - &pConnectionlist->pConnections[0];
pConnectionlist->pOpaqueCb(iConnID, &pReliableData->aData[0], uDataSize, pConnectionlist->pOpaqueUserData);
}
/*F********************************************************************************/
/*!
\Function _VoipReliableDataInboundTextProcess
\Description
Inbound processing of reliablity data of type VOIP_RELIABLE_TYPE_TEXT.
\Input *pConnectionlist - connectionlist ref
\Input *pConnection - connection on which the packet was received
\Input *pReliableData - pointer to inbound reliable data entry
\Version 05/02/2017 (mclouatre)
*/
/********************************************************************************F*/
static void _VoipReliableDataInboundTextProcess(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, ReliableDataT *pReliableData)
{
uint8_t uUserIndex = pReliableData->aData[0]; // first byte is user index
uint16_t uTextSize = _VoipDecodeU16(&pReliableData->info.uSize[0]) - 1; // text size is payload size minus the byte used for the user index
if (uTextSize > 0)
{
if (_VoipConnectionIsTranscribedTextRequested(pConnectionlist))
{
int32_t iConnID = pConnection - &pConnectionlist->pConnections[0];
if (pConnectionlist->uRecvMask & (1 << iConnID))
{
pConnectionlist->pTextCb(iConnID, uUserIndex, (char *)&pReliableData->aData[1], (void *)pConnectionlist->pTextUserData);
}
else
{
NetPrintf(("voipconnection: inbound transcribed text dropped because connection (%d) is muted\n", iConnID));
}
}
else
{
NetPrintf(("voipconnection: inbound transcribed text dropped because none of the local players requested for it (normal when voip is routed through DirytCast voipserver)\n"));
}
}
else
{
NetPrintf(("voipconnection: inbound transcribed text dropped because decoding resulted in an empty string\n"));
}
}
/*F********************************************************************************/
/*!
\Function _VoipReliableDataUnpackACKandACKCNF
\Description
Inbound processing of reliablity data - deals with ACK+ACKCNF protocol
components specifically.
\Input *pConnectionlist - connectionlist ref
\Input uSrcClientId - client id of originator
\Input uFlags - uFlags from the inbound packet
\Input bPing - TRUE if packet is a ping packet, FALSE if packet is a micr packet
\Input *pRead - pointer to next byte to be read in packet payload
\Output
uint8_t * - pointer to first byte after ACK
\Version 09/14/2014 (mclouatre)
*/
/********************************************************************************F*/
static uint8_t * _VoipReliableDataUnpackACKandACKCNF(VoipConnectionlistT *pConnectionlist, uint32_t uSrcClientId, uint8_t uFlags, uint32_t bPing, uint8_t *pRead)
{
int32_t iConnectionIndex;
VoipConnectionT *pConnection = NULL;
uint32_t bPeerIsAckingUs = FALSE;
// find connection matching the source client id
for (iConnectionIndex = 0; iConnectionIndex < pConnectionlist->iMaxConnections; iConnectionIndex++)
{
if (pConnectionlist->pConnections[iConnectionIndex].uRemoteClientId == uSrcClientId)
{
pConnection = &pConnectionlist->pConnections[iConnectionIndex];
break;
}
}
// is the packet buffer already filled with ACK+ACKCNF protocol components used for the reliability mechanism?
if (uFlags & VOIP_PACKET_RELIABLE_FLAG_ACK_ACKCNF)
{
int32_t iReliableAckEntriesCount = *pRead++;
int32_t iReliableAckEntryIndex;
for (iReliableAckEntryIndex = 0; iReliableAckEntryIndex < iReliableAckEntriesCount; iReliableAckEntryIndex++)
{
uint32_t uClientId;
ReliableAckT *pReliableAck = (ReliableAckT *)pRead;
pRead += sizeof(ReliableAckT);
if (pConnection != NULL)
{
// determine our local client id from the remote client's point of view
uint32_t uLocalClientId = pConnection->bIsLocalClientIdValid ? pConnection->uLocalClientId : pConnectionlist->uClientId;
// extract client id from ACK+ACKCNF protocol component entry
uClientId = _VoipDecodeU32(&pReliableAck->aRemoteClientId[0]);
// ignore the entry if it is not for us
if (uClientId == uLocalClientId)
{
uint8_t uAckedSeq;
// deal with ACKCNF
pConnection->bPeerNeedsAck = ((pReliableAck->uAckedSeq & VOIP_RELIABLE_ACKCNF_MASK) ? TRUE : FALSE);
// deal with ACK
uAckedSeq = pReliableAck->uAckedSeq & ~VOIP_RELIABLE_ACKCNF_MASK;
if (uAckedSeq != 0)
{
#if VOIP_RELIABLE_DEBUG
NetPrintf(("voipconnection: [%d] reliable ACK+ACKCNF (acked seq# %03d, %s) found in inbound %s packet from clientId=0x%08x\n",
iConnectionIndex, uAckedSeq, (pConnection->bPeerNeedsAck ?"SendAck":"HaltAck"), (bPing?"PNG":"MIC"), uSrcClientId));
#endif
bPeerIsAckingUs = TRUE;
while ( (pConnection->pOutboundReliableDataQueue != NULL) &&
(uAckedSeq >= pConnection->pOutboundReliableDataQueue->data.info.uSeq) )
{
_VoipConnectionReliableDataDequeue(pConnectionlist, iConnectionIndex);
}
}
else
{
#if VOIP_RELIABLE_DEBUG
NetPrintf(("voipconnection: [%d] reliable ACK+ACKCNF (acked seq# INV, %s) found in inbound %s packet from clientId=0x%08x\n",
iConnectionIndex, (pConnection->bPeerNeedsAck ?"SendAck":"HaltAck"), (bPing?"PNG":"MIC"), uSrcClientId));
#endif
}
}
}
else
{
NetPrintf(("voipconnection: [%d] dropped inbound ACK+ACKCNF - failed to find connection matching originating clientId=0x%08x\n", iConnectionIndex, uSrcClientId));
}
}
}
if (pConnection != NULL)
{
pConnection->bPeerIsAcking = bPeerIsAckingUs;
}
return(pRead);
}
/*F********************************************************************************/
/*!
\Function _VoipReliableDataUnpackDATA
\Description
Inbound processing of reliablity data - deals with DATA protocol component specifically.
\Input *pConnectionlist - connectionlist ref
\Input *pConnection - connection on which the packet was received
\Input uFlags - uFlags from the inbound packet
\Input bPing - TRUE if packet is a ping packet, FALSE if packet is a micr packet
\Input *pRead - pointer to next byte to be read in packet payload
\Output
uint8_t * - pointer to first byte after DATA
\Version 09/14/2014 (mclouatre)
*/
/********************************************************************************F*/
static uint8_t * _VoipReliableDataUnpackDATA(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, uint8_t uFlags, uint32_t bPing, uint8_t *pRead)
{
// is the packet buffer already filled with the ACK protocol component used for the reliability mechanism?
if (uFlags & VOIP_PACKET_RELIABLE_FLAG_DATA)
{
int32_t iReliableDataEntriesCount = *pRead++;
int32_t iReliableDataEntryIndex;
for (iReliableDataEntryIndex = 0; iReliableDataEntryIndex < iReliableDataEntriesCount; iReliableDataEntryIndex++)
{
ReliableDataT *pReliableData = (ReliableDataT *)pRead;
uint16_t uSize = _VoipDecodeU16(&pReliableData->info.uSize[0]);
uint32_t uClientId = _VoipDecodeU32(&pReliableData->info.aRemoteClientId[0]);
uint32_t uLocalClientId = pConnection->bIsLocalClientIdValid ? pConnection->uLocalClientId : pConnectionlist->uClientId;
// move read pointer ahead
pRead += sizeof(pReliableData->info) + uSize;
// make sure this is for us
if (uClientId == uLocalClientId)
{
if (pReliableData->info.uSeq == _VoipConnectionIncrementReliableSeqNb(pConnection->uInSeqNb))
{
// update next expected inbound reliable seq nb
pConnection->uInSeqNb = pReliableData->info.uSeq;
#if VOIP_RELIABLE_DEBUG
NetPrintf(("voipconnection: [%d] reliable DATA (seq# %03d, %s, %d bytes) found in inbound %s packet\n", (pConnection - &pConnectionlist->pConnections[0]),
pReliableData->info.uSeq, _strReliableType[pReliableData->info.uType], uSize, (bPing?"PNG":"MIC")));
#endif
if (pReliableData->info.uType == VOIP_RELIABLE_TYPE_USERADD)
{
_VoipReliableDataInboundUserAddProcess(pConnectionlist, pConnection, pReliableData);
}
else if (pReliableData->info.uType == VOIP_RELIABLE_TYPE_USERREM)
{
_VoipReliableDataInboundUserRemProcess(pConnectionlist, pConnection, pReliableData);
}
else if (pReliableData->info.uType == VOIP_RELIABLE_TYPE_OPAQUE)
{
_VoipReliableDataInboundOpaqueProcess(pConnectionlist, pConnection, pReliableData);
}
else if (pReliableData->info.uType == VOIP_RELIABLE_TYPE_TEXT)
{
_VoipReliableDataInboundTextProcess(pConnectionlist, pConnection, pReliableData);
}
else
{
NetPrintf(("voipconnection: [%d] unsupported inbound reliable data type (%d)\n", (pConnection - &pConnectionlist->pConnections[0]), pReliableData->info.uType));
}
}
else
{
#if VOIP_RELIABLE_DEBUG
NetPrintf(("voipconnection: [%d] dropped inbound reliable data embedded in %s packet - seq nb mismatch (got: %d - expected: %d)\n",
(pConnection - &pConnectionlist->pConnections[0]), (bPing ? "PNG" : "MIC"), pReliableData->info.uSeq, _VoipConnectionIncrementReliableSeqNb(pConnection->uInSeqNb)));
#endif
}
}
else
{
#if VOIP_RELIABLE_DEBUG
NetPrintf(("voipconnection: [%d] dropped inbound reliable data embedded in %s packet - client id mismatch (target: 0x%08x - local: 0x%08x)\n",
(pConnection - &pConnectionlist->pConnections[0]), (bPing ? "PNG" : "MIC"), uClientId, uLocalClientId));
#endif
}
}
}
return(pRead);
}
/*F********************************************************************************/
/*!
\Function _VoipReliableDataInProcess
\Description
Inbound processing of reliablity data. To be invoked every time a
ping packet or a micr packet is received. Reliability protocol components
(ACK, ACKCNF, DATA) included in the packet are going to be processed appropriately.
\Input *pConnectionlist - connectionlist ref
\Input *pConnection - connection on which the packet was received
\Input *pPacketHeader - pointer to header of the inbound packet
\Input *pPacketPayload - pointer to first payload byte of inbound packet
\Output
uint8_t - first byte following reliable data in packet payload
\Version 09/14/2014 (mclouatre)
*/
/********************************************************************************F*/
static uint8_t * _VoipReliableDataInProcess(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, VoipPacketHeadT *pPacketHeader, uint8_t *pPacketPayload)
{
// note: for voipserver-based topology, uSrcClientId may not be same as pConnection->uClientId
uint32_t uSrcClientId = _VoipDecodeU32(&pPacketHeader->aClientId[0]);
uint32_t bPing = ((pPacketHeader->aType[0] == 'P' && pPacketHeader->aType[1] == 'N' && pPacketHeader->aType[2] == 'G') ? TRUE : FALSE);
uint8_t *pRead = _VoipReliableDataUnpackACKandACKCNF(pConnectionlist, uSrcClientId, pPacketHeader->uFlags, bPing, pPacketPayload);
pRead = _VoipReliableDataUnpackDATA(pConnectionlist, pConnection, pPacketHeader->uFlags, bPing, pRead);
return(pRead);
}
/*F********************************************************************************/
/*!
\Function _VoipUpdateUserFlags
\Description
Update the flags field for each user just before serialization.
\Input *pConnectionlist - connectionlist ref
\Version 10/02/2019 (cvienneau)
*/
/********************************************************************************F*/
static void _VoipUpdateUserFlags(VoipConnectionlistT *pConnectionlist)
{
int32_t iUserIndex;
uint8_t bCrossPlay = VoipStatus(VoipGetRef(), 'xply', 0, NULL, 0);
for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS_EXTENDED; iUserIndex++)
{
if (pConnectionlist->aIsParticipating[iUserIndex] == TRUE)
{
// is cross play setup for the local users
if (bCrossPlay)
{
pConnectionlist->LocalUsers[iUserIndex].uFlags |= VOIPUSER_FLAG_CROSSPLAY;
}
}
}
}
/*F********************************************************************************/
/*!
\Function _VoipConnectionConn
\Description
Send a connection packet to peer
\Input *pConnectionlist - connectionlist ref
\Input *pConnection - pointer to connection to connect on
\Input uTick - current tick count
\Version 03/20/2004 (jbrookes)
*/
/********************************************************************************F*/
static void _VoipConnectionConn(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, uint32_t uTick)
{
VoipConnPacketT ConnPacket;
int32_t iUserIndex;
uint32_t uLocalClientId = 0;
uint32_t uSize = sizeof(ConnPacket) - sizeof(ConnPacket.LocalUsers) + (sizeof(*ConnPacket.LocalUsers) * VOIP_MAXLOCALUSERS_EXTENDED);
// output diagnostic info
NetPrintf(("voipconnection: [%d] (sess id 0x%08x) sending connect packet to remote console (%a:%d)\n",
VOIP_ConnID(pConnectionlist, pConnection), pConnection->uSessionId[0],
SocketNtohl(pConnection->SendAddr.sin_addr.s_addr), SocketNtohs(pConnection->SendAddr.sin_port)));
// send a connection packet
ds_memclr(&ConnPacket, sizeof(VoipConnPacketT));
ds_memcpy_s(&ConnPacket.Head, sizeof(ConnPacket.Head), &_Voip_ConnPacket, sizeof(_Voip_ConnPacket));
_VoipEncodeU32(ConnPacket.aRemoteClientId, pConnection->uRemoteClientId);
_VoipEncodeU32(ConnPacket.Head.aSessionId, pConnection->uSessionId[0]);
_VoipEncodeLocalHeadsetStatus(pConnectionlist, &(ConnPacket.Head));
if (_VoipConnectionIsTranscribedTextRequested(pConnectionlist))
{
ConnPacket.Head.uFlags |= VOIP_PACKET_STATUS_FLAG_STT;
}
_VoipUpdateUserFlags(pConnectionlist);
for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS_EXTENDED; iUserIndex++)
{
if (pConnectionlist->aIsParticipating[iUserIndex] != FALSE)
{
_VoipEncodeVoipUser(&ConnPacket.LocalUsers[iUserIndex], &pConnectionlist->LocalUsers[iUserIndex]);
}
}
ConnPacket.bConnected = pConnection->bConnPktRecv;
// extended local users always have an extra slot allocated and are not the same as maxlocalusers
ConnPacket.uNumLocalUsers = VOIP_MAXLOCALUSERS_EXTENDED;
#if VOIP_CONNECTION_DEBUG
NetPrintMem(&ConnPacket, sizeof(ConnPacket), "connection packet data");
#endif
uLocalClientId = pConnection->uLocalClientId == 0 ? pConnectionlist->uClientId : pConnection->uLocalClientId;
pConnection->iSendResult = _VoipSocketSendto(pConnectionlist->pSocket, uLocalClientId, &ConnPacket.Head, uSize, 0, (struct sockaddr *)&pConnection->SendAddr, sizeof(pConnection->SendAddr));
// update timestamp
pConnection->uLastSend = uTick;
}
/*F********************************************************************************/
/*!
\Function _VoipConnectionDisc
\Description
Send a disconnection request to peer.
\Input iConnId - connection id
\Input uClientId - client id
\Input uRemoteClientId - remote client id
\Input uSessionId - session identifier
\Input *pSendAddr - address to send disconnect to
\Input *pSocket - socket to send request with
\Version 03/21/2004 (jbrookes)
*/
/********************************************************************************F*/
static void _VoipConnectionDisc(int32_t iConnId, uint32_t uClientId, uint32_t uRemoteClientId, uint32_t uSessionId, struct sockaddr_in *pSendAddr, SocketT *pSocket)
{
VoipDiscPacketT DiscPacket;
// send a disconnect packet
NetPrintf(("voipconnection: [%d] (sess id 0x%08x) sending disconnect packet to %a:%d\n",
iConnId, uSessionId, SocketNtohl(pSendAddr->sin_addr.s_addr), SocketNtohs(pSendAddr->sin_port)));
// set up disc packet
ds_memcpy_s(&DiscPacket.Head, sizeof(DiscPacket.Head), &_Voip_DiscPacket, sizeof(_Voip_DiscPacket));
_VoipEncodeU32(DiscPacket.aRemoteClientId, uRemoteClientId);
_VoipEncodeU32(DiscPacket.Head.aSessionId, uSessionId);
// send disc packet
_VoipSocketSendto(pSocket, uClientId, &DiscPacket.Head, sizeof(DiscPacket), 0, (struct sockaddr *)pSendAddr, sizeof(*pSendAddr));
}
/*F********************************************************************************/
/*!
\Function _VoipConnectionInit
\Description
Transition from connecting to connected.
\Input *pConnectionlist - connectionlist connection is a part of
\Input *pConnection - connection to transition
\Input iConnID - index of connection
\Version 03/21/2004 (jbrookes)
*/
/********************************************************************************F*/
static void _VoipConnectionInit(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, int32_t iConnID)
{
int32_t iLocalUserIndex;
NetPrintf(("voipconnection: [%d] connection established; talking to remote user(s)\n", iConnID));
#if DIRTYCODE_LOGGING
_VoipConnectionPrintRemoteUsers(pConnection, iConnID);
#endif
// register the new remote users with voipheadset
VoipConnectionRegisterRemoteTalkers(pConnectionlist, iConnID, TRUE);
// we want to call this before the connection goes to state ST_ACTV
// to avoid race conditions where the voipthread would start working with this conn before the channel config is updated
VoipCommonApplyChannelConfig((VoipCommonRefT *)VoipGetRef());
// update connection flags
pConnection->uRemoteConnStatus = VOIP_CONN_CONNECTED;
// flag that this connection shall now be considered when updating friend status bitmask
if (pConnectionlist->iFriendConnId == VOIP_CONNID_NONE)
{
pConnectionlist->iFriendConnId = iConnID;
}
else
{
pConnectionlist->iFriendConnId = VOIP_CONNID_ALL;
}
// initialize timer used for sending at a fixed 10hz rate
for (iLocalUserIndex = 0; iLocalUserIndex < VOIP_MAXLOCALUSERS_EXTENDED; iLocalUserIndex++)
{
pConnection->aVoiceSendTimer[iLocalUserIndex] = NetTick();
}
/*
To avoid multi-threading race conditions, it is important to only move the connection state to ST_ACTV at the end of
the function (after initializing all connection vars and updating channel config) because some code exercised by the
_VoipThread (like _VoipUpdateRemoteStatus()) may start to use these fields concurrently as soon as the connection state
becomes ST_ACTV
*/
pConnection->eState = ST_ACTV;
}
/*F********************************************************************************/
/*!
\Function _VoipConnectionPing
\Description
Ping a connected remote peer.
\Input *pConnectionlist - connectionlist ref
\Input *pConnection - pointer to connection to send on
\Input uTick - current tick count
\Version 03/20/2004 (jbrookes)
*/
/********************************************************************************F*/
static void _VoipConnectionPing(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, uint32_t uTick)
{
VoipPingPacketT PingPacket;
int32_t iUserIndex;
uint32_t uChannelUserIndices = 0;
uint8_t *pWrite = PingPacket.aData;
const uint8_t *pEnd = pWrite+sizeof(PingPacket.aData);
uint32_t uLocalClientId = pConnection->bIsLocalClientIdValid ? pConnection->uLocalClientId : pConnectionlist->uClientId;
#if VOIP_CONNECTION_DEBUG
NetPrintf(("voipconnection: [%d] (sess id 0x%08x) pinging %a:%d\n",
VOIP_ConnID(pConnectionlist, pConnection), pConnection->uSessionId[0],
SocketNtohl(pConnection->SendAddr.sin_addr.s_addr), SocketNtohs(pConnection->SendAddr.sin_port)));
#endif
// set up ping packet
ds_memcpy_s(&PingPacket.Head, sizeof(PingPacket.Head), &_Voip_PingPacket, sizeof(_Voip_PingPacket));
_VoipEncodeLocalHeadsetStatus(pConnectionlist, &(PingPacket.Head));
_VoipEncodeU32(PingPacket.aRemoteClientId, pConnection->uRemoteClientId);
_VoipEncodeU32(PingPacket.Head.aSessionId, pConnection->uSessionId[0]);
if (_VoipConnectionIsTranscribedTextRequested(pConnectionlist))
{
PingPacket.Head.uFlags |= VOIP_PACKET_STATUS_FLAG_STT;
}
for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS_EXTENDED; iUserIndex++)
{
/* write the channel information for the participating user indices and
set the bit so the receiving side knows which index it corresponds to */
if (pConnectionlist->aIsParticipating[iUserIndex])
{
uChannelUserIndices |= 1 << iUserIndex;
_VoipEncodeU32(pWrite, pConnectionlist->aChannels[iUserIndex]);
pWrite += 4;
}
}
_VoipEncodeU32(PingPacket.aChannelUserIndices, uChannelUserIndices);
// if needed, add reliable protocol components to this ping packet
pWrite = _VoipReliableDataOutProcess(pConnectionlist, (pConnection - &pConnectionlist->pConnections[0]), &PingPacket.Head, pWrite, pEnd - pWrite);
// send a 'ping' packet
pConnection->iSendResult = _VoipSocketSendto(pConnectionlist->pSocket, uLocalClientId, &PingPacket.Head, (pWrite - (uint8_t *)&PingPacket), 0, (struct sockaddr *)&pConnection->SendAddr, sizeof(pConnection->SendAddr));
// update send timestamp and local status flag
pConnection->uLastSend = uTick;
for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS; ++iUserIndex)
{
if (NetTickDiff(uTick, pConnectionlist->uLocalUserLastSend[iUserIndex]) > VOIP_PING_RATE)
{
pConnectionlist->uLocalUserStatus[iUserIndex] &= ~VOIP_LOCAL_USER_SENDVOICE;
}
}
}
/*F********************************************************************************/
/*!
\Function _VoipConnectionIsSendPktReady
\Description
Determine if we should send the current packet.
\Input *pConnectionlist - connectionlist ref
\Input *pConnection - connection pointer
\Input iUserIndex - index of local user that generated the voice to be sent
\Input uTick - current tick count
\Output
uint32_t - TRUE if ready, FALSE otherwise.
\Version 10/01/2011 (mclouatre)
*/
/********************************************************************************F*/
static uint32_t _VoipConnectionIsSendPktReady(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, int32_t iUserIndex, uint32_t uTick)
{
VoipMicrPacketT *pVoipPkt = &pConnection->VoipMicrPacket[iUserIndex];
uint8_t *pWrite = _VoipPacketGetWritePtr(pVoipPkt);
uint8_t bVariableSubPktLen = ((pVoipPkt->MicrInfo.uSubPacketSize == 0xFF) ? TRUE : FALSE); // check if sub-pkts in this packet are fixed length or variable length
int32_t iEstimatedResultingPacketPayloadSize;
// if an additional sub-packet would cause us to overflow the sub-packet buffer, send packet
if (bVariableSubPktLen)
{
// is next packet going to overflow? 1 byte for prepended size + estimated subpkt size
iEstimatedResultingPacketPayloadSize = (pWrite + 1 + pConnection->iMaxSubPktSize) - pVoipPkt->aData;
}
else
{
iEstimatedResultingPacketPayloadSize = (pWrite + pVoipPkt->MicrInfo.uSubPacketSize) - pVoipPkt->aData;
}
if (iEstimatedResultingPacketPayloadSize > (signed)sizeof(pVoipPkt->aData))
{
return(TRUE);
}
// if 10hz timer has elapsed, send packet
if ((pVoipPkt->MicrInfo.uNumSubPackets > 0) && (NetTickDiff(uTick, pConnection->aVoiceSendTimer[iUserIndex]) >= 0))
{
return(TRUE);
}
// not yet ready to send
return(FALSE);
}
/*F********************************************************************************/
/*!
\Function _VoipConnectionSendSingle
\Description
Send a single buffered voice packet, if it is time to send it.
\Input *pConnectionlist - connectionlist ref
\Input *pConnection - pointer to connection to send on
\Input iUserIndex - index of the local user that generated the voice data to be sent
\Input uTick - current tick count
\Input bFlush - flush data
\Version 03/20/2004 (jbrookes)
*/
/********************************************************************************F*/
static void _VoipConnectionSendSingle(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, int32_t iUserIndex, uint32_t uTick, uint32_t bFlush)
{
// ref current buffered packet
VoipMicrPacketT *pPacket = &pConnection->VoipMicrPacket[iUserIndex];
uint32_t bSendPacket = FALSE;
int32_t iParticipatingUserIndex;
uint32_t uLocalClientId = pConnection->bIsLocalClientIdValid ? pConnection->uLocalClientId : pConnectionlist->uClientId;
// should we send?
if (_VoipConnectionIsSendPktReady(pConnectionlist, pConnection, iUserIndex, uTick) || (bFlush == TRUE))
{
bSendPacket = TRUE;
}
// send packet?
if (bSendPacket == TRUE)
{
int32_t iPacketSize, iValidPayloadSize;
uint32_t uVoipServerReadySendMask = 0;
// set packet sequence
pPacket->MicrInfo.uSeqn = pConnection->uSendSeqn++;
// set the user index
if (iUserIndex == VOIP_SHARED_USER_INDEX)
{
pPacket->MicrInfo.uUserIndex = VOIP_SHARED_REMOTE_INDEX;
}
else
{
pPacket->MicrInfo.uUserIndex = iUserIndex;
}
// set the channels
_VoipEncodeU32(pPacket->MicrInfo.channels.aChannelId, pConnectionlist->aChannels[iUserIndex]);
// set the session id
_VoipEncodeU32(pPacket->Head.aSessionId, pConnection->uSessionId[0]);
// flag whether one of the local users is requesting transcribed text
if (_VoipConnectionIsTranscribedTextRequested(pConnectionlist))
{
pPacket->Head.uFlags |= VOIP_PACKET_STATUS_FLAG_STT;
}
// set send mask for VoipServer use
if (pConnection->iVoipServerConnId != VOIP_CONNID_NONE)
{
uVoipServerReadySendMask = _VoipPrepareVoipServerSendMask(pConnectionlist);
}
_VoipEncodeU32(pPacket->MicrInfo.aSendMask, uVoipServerReadySendMask);
_VoipEncodeLocalHeadsetStatus(pConnectionlist, &pPacket->Head);
#if VOIP_CONNECTION_DEBUG
// display verbose status (metered to about once every three seconds)
if ((pConnection->uSendSeqn % 30) == 0)
{
char strSubPktSize[32];
sprintf(strSubPktSize, "size %d (bytes)", pPacket->MicrInfo.uSubPacketSize);
NetPrintf(("voipconnection: [%d] (sess id 0x%08x) local usr %d sending pkt (seq#:%d) with %d sub-pkt(s) of %s to %a:%d | voip channels:0x%08x\n",
VOIP_ConnID(pConnectionlist, pConnection), pConnection->uSessionId[0], iUserIndex, pPacket->MicrInfo.uSeqn, pPacket->MicrInfo.uNumSubPackets,
(pPacket->MicrInfo.uSubPacketSize == 0xFF ? "variable-length" : strSubPktSize), SocketNtohl(pConnection->SendAddr.sin_addr.s_addr), SocketNtohs(pConnection->SendAddr.sin_port),
pConnectionlist->aChannels[iUserIndex]));
}
#endif
// send voice packet
iValidPayloadSize = _VoipPacketGetWritePtr(&pConnection->VoipMicrPacket[iUserIndex]) - &pPacket->aData[0];
iPacketSize = sizeof(pConnection->VoipMicrPacket[iUserIndex]) - sizeof(pPacket->aData) + iValidPayloadSize;
pConnection->iSendResult = _VoipSocketSendto(pConnectionlist->pSocket, uLocalClientId, &pPacket->Head,
iPacketSize, 0, (struct sockaddr *)&pConnection->SendAddr, sizeof(pConnection->SendAddr));
// update send info -- don't update uLastSend in server mode, to force pinging even when voice is being sent
if (pConnection->iVoipServerConnId == VOIP_CONNID_NONE)
{
pConnection->uLastSend = uTick;
}
// if packet contains reliable data, don't update aVoiceSendTimer to make sure we retry sending voip sub-packets soon (in case reliable data took all the place in the packet payload)
if ((pPacket->Head.uFlags & VOIP_PACKET_RELIABLE_FLAG_DATA) == 0)
{
pConnection->aVoiceSendTimer[iUserIndex] = uTick + VOIP_MSPERPACKET;
}
// if the voice packet is from a shared user set all the participating user's status
if (iUserIndex == VOIP_SHARED_USER_INDEX)
{
for (iParticipatingUserIndex = 0; iParticipatingUserIndex < VOIP_MAXLOCALUSERS; ++iParticipatingUserIndex)
{
if (pConnectionlist->aIsParticipating[iParticipatingUserIndex] == TRUE)
{
#if defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK)
/* For xboxone, opaque traffic is always sent as if it was originated from the
"shared user". We need to query VoipHeadset (via VoipCommon) to find out which
local user is exactly detected as talking by the MS game chat 2's chat manager
and consequently involved in generating the current outbound flow of voip packets. */
if (VoipCommonStatus((VoipCommonRefT *)VoipGetRef(), 'talk', iParticipatingUserIndex, NULL, 0))
#endif
{
pConnectionlist->uLocalUserStatus[iParticipatingUserIndex] |= VOIP_LOCAL_USER_SENDVOICE;
pConnectionlist->uLocalUserLastSend[iParticipatingUserIndex] = uTick;
}
}
}
}
else
{
pConnectionlist->uLocalUserStatus[iUserIndex] |= VOIP_LOCAL_USER_SENDVOICE;
pConnectionlist->uLocalUserLastSend[iUserIndex] = uTick;
}
// reset buffer used to build voip micr packet
ds_memclr(&pPacket->MicrInfo, sizeof(pPacket->MicrInfo)); // clear micr packet info
pPacket->Head.uFlags = 0; // clear flags in packet header
}
}
/*F********************************************************************************/
/*!
\Function _VoipConnectionRecvSingle
\Description
Handle data incoming on a connection.
\Input *pConnectionlist - connectionlist connection belongs to
\Input *pConnection - pointer to connection to receive on
\Input *pSendAddr - remote address data was received from
\Input *pPacket - packet data that was received
\Input iSize - size of data received
\Version 03/21/2004 (jbrookes)
*/
/********************************************************************************F*/
static void _VoipConnectionRecvSingle(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, struct sockaddr_in *pSendAddr, VoipPacketBufferT *pPacket, int32_t iSize)
{
int32_t iConnID = VOIP_ConnID(pConnectionlist, pConnection);
uint32_t uSessionId = _VoipDecodeU32(pPacket->VoipPacketHead.aSessionId);
#if DIRTYCODE_LOGGING
uint32_t uClientId = _VoipDecodeU32(pPacket->VoipPacketHead.aClientId);
const char *pStrPktType;
#endif
uint32_t uTick;
uint8_t uPktType;
uint32_t uIndex;
// note time of arrival
uTick = NetTick();
// count the packet
pConnection->iRecvPackets += 1;
pConnection->iRecvData += iSize;
// did port change?
if (pConnection->SendAddr.sin_port != pSendAddr->sin_port)
{
NetPrintf(("voipconnection: [%d] port remap #%d: %d bytes from %d instead of %d\n", iConnID,
pConnection->iChangePort++, iSize,
SocketNtohs(pSendAddr->sin_port),
SocketNtohs(pConnection->SendAddr.sin_port)));
// send future to remapped port
pConnection->SendAddr.sin_port = pSendAddr->sin_port;
// reset counter of packets received
pConnection->iRecvPackets = 0;
}
if (pPacket->VoipPacketHead.uFlags & VOIP_PACKET_STATUS_FLAG_STT)
{
#if DIRTYCODE_LOGGING
if (pConnection->bTranscribedTextRequested == FALSE)
{
NetPrintf(("voipconnection: [%d] one or several of the remote peers on that connection are requesting transcribed text\n", iConnID));
}
#endif
pConnection->bTranscribedTextRequested = TRUE;
}
else
{
#if DIRTYCODE_LOGGING
if (pConnection->bTranscribedTextRequested != FALSE)
{
NetPrintf(("voipconnection: [%d] remote peer(s) are no longer requesting transcribed text\n", iConnID));
}
#endif
pConnection->bTranscribedTextRequested = FALSE;
}
// identify type of incoming packet (connect, disconnect, ping, mic or unknown)
if (VOIP_SamePacketType(&pPacket->VoipPacketHead, &_Voip_ConnPacket))
{
uPktType = 'C'; // connection packet
#if DIRTYCODE_LOGGING
pStrPktType = "connect";
#endif
}
else if (VOIP_SamePacketType(&pPacket->VoipPacketHead, &_Voip_PingPacket))
{
uPktType = 'P'; // ping packet
#if DIRTYCODE_LOGGING
pStrPktType = "ping";
#endif
}
else if (VOIP_SamePacketType(&pPacket->VoipPacketHead, &_Voip_MicrPacket))
{
uPktType = 'M'; // data packet
#if DIRTYCODE_LOGGING
pStrPktType = "data";
#endif
}
else if (VOIP_SamePacketType(&pPacket->VoipPacketHead, &_Voip_DiscPacket))
{
uPktType = 'D'; // disconnect packet
#if DIRTYCODE_LOGGING
pStrPktType = "disconnect";
#endif
}
else
{
NetPrintf(("voipconnection: [%d] received unknown packet type 0x%02x%02x%02x\n", iConnID,
pPacket->VoipPacketHead.aType[0], pPacket->VoipPacketHead.aType[1], pPacket->VoipPacketHead.aType[2]));
return;
}
// ignore packet if session id does not match
if (_VoipConnectionMatchSessionId(pConnectionlist, iConnID, uSessionId) == FALSE)
{
#if DIRTYCODE_LOGGING
NetPrintf(("voipconnection: [%d] (sess id 0x%08x) ignoring packet because of session IDs mismatch\n",
iConnID, uSessionId, pStrPktType));
// print set of sessions sharing this connection
_VoipConnectionPrintSessionIds(pConnectionlist, iConnID);
NetPrintMem(pPacket, iSize, "packet data");
#endif
return;
}
// handle incoming packet
switch (uPktType)
{
// connect packet
case 'C':
{
VoipConnPacketT *pConnPacket = &pPacket->VoipConnPacket;
int32_t iRemoteUserIndex;
// got a connection packet
NetPrintf(("voipconnection: [%d] (sess id 0x%08x) received conn packet bConnected=%d eState=%d uClientId=0x%08x\n",
iConnID, uSessionId, pConnPacket->bConnected, pConnection->eState, uClientId));
// copy remote user's info
pConnection->iMaxRemoteUsers = pConnPacket->uNumLocalUsers - 1;
pConnection->iMaxRemoteUsersExt = pConnection->iMaxRemoteUsers;
for (iRemoteUserIndex = 0; iRemoteUserIndex < pConnection->iMaxRemoteUsersExt; iRemoteUserIndex++)
{
_VoipDecodeVoipUser(&pConnPacket->LocalUsers[iRemoteUserIndex], &pConnection->RemoteUsers[iRemoteUserIndex]);
}
_VoipDecodeRemoteHeadsetStatus(pConnection, &pConnPacket->Head);
// flag that we received Conn packet from remote party
pConnection->bConnPktRecv = TRUE;
// if they've received data from us, consider the connection established
if ((pConnPacket->bConnected) && (pConnection->eState == ST_CONN))
{
_VoipConnectionInit(pConnectionlist, pConnection, iConnID);
}
// if peer no more sees us as connected... go back to ST_CONN state and hope for connection to go back up
if ((!pConnPacket->bConnected) && (pConnection->eState == ST_ACTV))
{
NetPrintf(("voipconnection: [%d] voip connection lost! try to re-establish. connection back to ST_CONN state (from ST_ACTV)... \n", iConnID));
pConnection->eState = ST_CONN;
}
// update last received time
pConnection->uLastRecv = uTick;
}
break;
// ping packet
case 'P':
{
VoipPingPacketT *pPingPacket = &pPacket->VoipPingPacket;
int32_t iUserIndex;
uint32_t uChannelUserIndices;
const uint8_t *pRead = pPingPacket->aData;
#if VOIP_CONNECTION_DEBUG
char strGlobalVoipChannelConfig[80];
char strUserVoipChannelConfig[20];
ds_memclr(strGlobalVoipChannelConfig, sizeof(strGlobalVoipChannelConfig));
ds_memclr(strUserVoipChannelConfig, sizeof(strUserVoipChannelConfig));
NetPrintf(("voipconnection: [%d] (sess id 0x%08x) received ping packet from clientId=0x%08x\n", iConnID, uSessionId, uClientId));
#endif
if (pConnection->bConnPktRecv)
{
VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)VoipGetRef();
// if we receive a ping in connection state, that means we're connected
if (pConnection->eState == ST_CONN)
{
_VoipConnectionInit(pConnectionlist, pConnection, iConnID);
}
// update connection status flags
_VoipDecodeRemoteHeadsetStatus(pConnection, &pPingPacket->Head);
// check to see if VOIP_REMOTE_USER_RECVVOICE flag should be deactivated for other users
for (uIndex = 0; uIndex < VOIP_MAXLOCALUSERS; ++uIndex)
{
if (NetTickDiff(uTick, pConnection->uUserLastRecv[uIndex]) > VOIP_PING_RATE)
{
pConnection->uRemoteUserStatus[uIndex] &= ~VOIP_REMOTE_USER_RECVVOICE;
}
}
pConnection->uRemoteConnStatus |= VOIP_CONN_BROADCONN;
pConnection->uLastRecv = uTick;
// read the bitset to figure which channel configurations need to be updated
uChannelUserIndices = _VoipDecodeU32(pPingPacket->aChannelUserIndices);
// update channel config for each remote user and remote shared user
for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS_EXTENDED; iUserIndex++)
{
if ((uChannelUserIndices >> iUserIndex) & 1)
{
pConnection->aRecvChannels[iUserIndex] = _VoipDecodeU32(pRead);
pRead += 4;
#if VOIP_CONNECTION_DEBUG
ds_snzprintf(strUserVoipChannelConfig, sizeof(strUserVoipChannelConfig), "user#%d:0x%08x ", iUserIndex, pConnection->aRecvChannels[iUserIndex]);
ds_strnzcat(strGlobalVoipChannelConfig, strUserVoipChannelConfig, sizeof(strGlobalVoipChannelConfig));
#endif
}
}
#if VOIP_CONNECTION_DEBUG
NetPrintf((" voip channels: %s\n", strGlobalVoipChannelConfig));
#endif
// process any reliable protocol components that may be included in this ping packet
_VoipReliableDataInProcess(pConnectionlist, pConnection, &pPingPacket->Head, (uint8_t *)pRead);
// update the channel
if (memcmp(&pConnection->aRecvChannels, &pConnection->aLastRecvChannels, sizeof(pConnection->aRecvChannels)))
{
VoipCommonProcessChannelChange(pVoipCommon, iConnID);
ds_memcpy_s(&pConnection->aLastRecvChannels, sizeof(pConnection->aLastRecvChannels), &pConnection->aRecvChannels, sizeof(pConnection->aRecvChannels));
VoipCommonUpdateRemoteStatus(pVoipCommon);
}
}
else
{
NetPrintf(("voipconnection: [%d] (sess id 0x%08x) ping packet from clientId=0x%08x ignored because conn packet not yet received\n",
iConnID, uSessionId, uClientId));
}
}
break;
// data packet
case 'M':
{
VoipMicrPacketT *pMicrPacket = &pPacket->VoipMicrPacket;
VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)VoipGetRef();
#if VOIP_CONNECTION_DEBUG
if ((pConnection->iRecvPackets % 30) == 0)
{
char strSubPktSize[32];
sprintf(strSubPktSize, "size %d (bytes)", pMicrPacket->MicrInfo.uSubPacketSize);
NetPrintf(("voipconnection: [%d] (sess id 0x%08x) received pkt (seq#:%d) with %d sub-pkt(s) of %s from remote usr idx %d | voip channels:0x%08x\n",
iConnID, uSessionId, pMicrPacket->MicrInfo.uSeqn, pMicrPacket->MicrInfo.uNumSubPackets, (pMicrPacket->MicrInfo.uSubPacketSize == 0xFF ? "variable-length" : strSubPktSize),
pMicrPacket->MicrInfo.uUserIndex, _VoipDecodeU32(pMicrPacket->MicrInfo.channels.aChannelId)));
NetPrintMem(pMicrPacket, iSize, "mic packet data");
}
#endif
if (pConnection->bConnPktRecv)
{
uint8_t *pRead;
int32_t iRemoteUserIndex;
uint32_t uLocalHeadsetStatusAggregate = 0;
// if we receive voice data in connection state, that means we're connected
if (pConnection->eState == ST_CONN)
{
_VoipConnectionInit(pConnectionlist, pConnection, iConnID);
}
// update connection status flags
_VoipDecodeRemoteHeadsetStatus(pConnection, &pMicrPacket->Head);
pConnection->uRemoteConnStatus |= (VOIP_CONN_ACTIVE|VOIP_CONN_BROADCONN);
if (pMicrPacket->MicrInfo.uUserIndex == VOIP_SHARED_REMOTE_INDEX)
{
pConnection->aRecvChannels[VOIP_SHARED_USER_INDEX] = _VoipDecodeU32(pMicrPacket->MicrInfo.channels.aChannelId);
}
else
{
pConnection->aRecvChannels[pMicrPacket->MicrInfo.uUserIndex] = _VoipDecodeU32(pMicrPacket->MicrInfo.channels.aChannelId);
}
// update the channel
if (memcmp(&pConnection->aRecvChannels, &pConnection->aLastRecvChannels, sizeof(pConnection->aRecvChannels)))
{
VoipCommonProcessChannelChange(pVoipCommon, iConnID);
ds_memcpy_s(&pConnection->aLastRecvChannels, sizeof(pConnection->aLastRecvChannels), &pConnection->aRecvChannels, sizeof(pConnection->aRecvChannels));
VoipCommonUpdateRemoteStatus(pVoipCommon);
}
pConnection->uLastRecv = uTick;
// validate and update packet sequence number
if (pMicrPacket->MicrInfo.uSeqn != (uint8_t)(pConnection->uRecvSeqn+1))
{
NetPrintf(("voipconnection: [%d] (sess id 0x%08x) got packet seq # %d while expecting packet seq # %d\n",
iConnID, uSessionId, pMicrPacket->MicrInfo.uSeqn, (uint8_t)pConnection->uRecvSeqn+1));
}
pConnection->uRecvSeqn = pMicrPacket->MicrInfo.uSeqn;
// process any reliable protocol components that may be included in this MIC packet
pRead = _VoipReliableDataInProcess(pConnectionlist, pConnection, &pMicrPacket->Head, &pMicrPacket->aData[0]);
/* is the connection active, and are we sending to it?
xboxone: After rebasing VoipHeadsetXboxOne to MS game chat 2, per-connection muting
is handled by VoipHeadsetXboxOne (See usage of +snd, -snd, +rcv, -rcv).
But for crossplay we want the muting to be applied here just like other plaforms.
Testing has shown that apply te send mask and +snd, -snd, +rcv, -rcv has no negative effect.*/
if (pConnectionlist->uRecvMask & (1 << iConnID))
{
// if the voice packet is from a remote shared user update all the remote user's status
if (pMicrPacket->MicrInfo.uUserIndex == VOIP_SHARED_REMOTE_INDEX)
{
for (iRemoteUserIndex = 0; iRemoteUserIndex < pConnection->iMaxRemoteUsers; ++iRemoteUserIndex)
{
VoipUserT *pVoipUser = (VoipUserT *)&pConnection->RemoteUsers[iRemoteUserIndex];
if (!VOIP_NullUser(pVoipUser))
{
#if defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK)
/* For xboxone, opaque traffic is always received as if it was originated from the
"shared user". We need to query VoipHeadset (via VoipCommon) to find out which
remote user is exactly detected as talking by the MS game chat 2's chat manager
and consequently involved in generating the current inbound flow of voip packets. */
if (VoipCommonStatus((VoipCommonRefT *)VoipGetRef(), 'talk', 0, pVoipUser, sizeof(*pVoipUser)))
#endif
{
pConnection->uRemoteUserStatus[iRemoteUserIndex] |= VOIP_REMOTE_USER_RECVVOICE;
pConnection->uUserLastRecv[iRemoteUserIndex] = uTick;
}
}
}
}
else
{
pConnection->uUserLastRecv[pMicrPacket->MicrInfo.uUserIndex] = uTick;
pConnection->uRemoteUserStatus[pMicrPacket->MicrInfo.uUserIndex] |= VOIP_REMOTE_USER_RECVVOICE;
}
// if there is at least one headset capable of outputting voice data, forward the packet along, ignore otherwise
// we do this to ignore spamming on PC (ticket GOS-30000)
for (uIndex = 0; uIndex < VOIP_MAXLOCALUSERS; ++uIndex)
{
uLocalHeadsetStatusAggregate |= pVoipCommon->Connectionlist.uLocalUserStatus[uIndex];
}
if (uLocalHeadsetStatusAggregate & VOIP_LOCAL_USER_OUTPUTDEVICEOK)
{
pConnectionlist->pVoiceCb((VoipUserT *)&pConnection->RemoteUsers[0], pConnection->iMaxRemoteUsers, iConnID, &pMicrPacket->MicrInfo, pRead, pConnectionlist->pVoiceUserData);
}
}
// check to see if VOIP_REMOTE_USER_RECVVOICE flag should be deactivated for other users
for (uIndex = 0; uIndex < VOIP_MAXLOCALUSERS; ++uIndex)
{
if (NetTickDiff(uTick, pConnection->uUserLastRecv[uIndex]) > VOIP_PING_RATE)
{
pConnection->uRemoteUserStatus[uIndex] &= ~VOIP_REMOTE_USER_RECVVOICE;
}
}
}
else
{
NetPrintf(("voipconnection: [%d] (sess id 0x%08x) voip data packet from clientId=0x%08x ignored because conn packet not yet received\n",
iConnID, uSessionId, uClientId));
}
}
break;
case 'D':
{
VoipDiscPacketT *pDiscPacket = &pPacket->VoipDiscPacket;
uint32_t uRemoteClientId = _VoipDecodeU32(pDiscPacket->aRemoteClientId);
uint32_t uLocalClientId = pConnection->bIsLocalClientIdValid ? pConnection->uLocalClientId : pConnectionlist->uClientId;
if (uRemoteClientId == uLocalClientId)
{
NetPrintf(("voipconnection: [%d] (sess id 0x%08x) remote peer 0x%08x has disconnected from 0x%08x\n",
iConnID, uSessionId, uClientId, uRemoteClientId));
// In this specific case, we know that remote peer is already in 'disconnected' state.
// No need to send back a DISC msg. Therefore, set bSendDiscMsg parameter to FALSE.
_VoipConnectionStop(pConnectionlist, &pConnectionlist->pConnections[iConnID], iConnID, FALSE);
}
else
{
// this should only occurs with multiple machines on the same PC and voip being routed to the incorrect client
NetPrintf(("voipconnection: [%d] (sess id 0x%08x) remote peer 0x%08x has disconnected from 0x%08x, but we are 0x%08x\n",
iConnID, uSessionId, uClientId, uRemoteClientId, pConnectionlist->uClientId));
}
}
break;
default:
{
NetPrintf(("voipconnection: [%d] critical error! this path can't be taken in _VoipConnectionRecvSing()\n"));
}
break;
}
}
/*F********************************************************************************/
/*!
\Function _VoipConnectionUpdateSingle
\Description
Update a virtual connection.
\Input *pConnectionlist - connectionlist
\Input *pConnection - pointer to connection to update
\Input iConnId - index of connection to update
\Input uTick - current tick count
\Output
uint32_t - nonzero=receiving voice, zero=not receiving voice
\Version 03/20/2004 (jbrookes)
*/
/********************************************************************************F*/
static uint32_t _VoipConnectionUpdateSingle(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, int32_t iConnId, uint32_t uTick)
{
int32_t iTimeout;
int32_t iUserIndex;
uint8_t bIsReceivingVoice = FALSE;
// make sure it's a valid connection
if (pConnection->eState == ST_DISC)
{
return(0);
}
// see if we need to send a conn or ping packet
if ((uTick - pConnection->uLastSend) > VOIP_PING_RATE)
{
// if we're connecting, send connection packet
if (pConnection->eState == ST_CONN)
{
_VoipConnectionConn(pConnectionlist, pConnection, uTick);
}
else
{
_VoipConnectionPing(pConnectionlist, pConnection, uTick);
}
}
// see if we need to send any buffered voice data
for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS_EXTENDED; iUserIndex++)
{
_VoipConnectionSendSingle(pConnectionlist, pConnection, iUserIndex, uTick, FALSE);
}
// see if we need to time out the connection
iTimeout = (pConnection->eState == ST_ACTV) ? pConnectionlist->iDataTimeout : VOIP_CONNTIMEOUT;
if (NetTickDiff(uTick, pConnection->uLastRecv) > iTimeout)
{
NetPrintf(("voipconnection: [%d] timing out connection due to inactivity\n", iConnId));
_VoipConnectionStop(pConnectionlist, &pConnectionlist->pConnections[iConnId], iConnId, TRUE);
}
// return if we are receiving voice or not
for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS; iUserIndex++)
{
if (pConnection->uRemoteUserStatus[iUserIndex] & VOIP_REMOTE_USER_RECVVOICE)
{
bIsReceivingVoice = TRUE;
break;
}
}
return((bIsReceivingVoice == TRUE) ? (1 << iConnId) : 0);
}
/*F********************************************************************************/
/*!
\Function _VoipConnectionRecv
\Description
Receive data and forward it to the appropriate connection.
\Input *pConnectionlist - connectionlist
\Output
int32_t - amount of data received
\Version 03/21/2004 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _VoipConnectionRecv(VoipConnectionlistT *pConnectionlist)
{
struct sockaddr_in RecvAddr;
int32_t iAddrLen, iRecvSize;
VoipPacketBufferT RecvPacket;
// no socket? no way to receive.
if (pConnectionlist->pSocket == NULL)
{
return(0);
}
// try and receive from a peer
if ((iRecvSize = SocketRecvfrom(pConnectionlist->pSocket, (char *)&RecvPacket, sizeof(RecvPacket), 0, (struct sockaddr *)&RecvAddr, (iAddrLen=sizeof(RecvAddr),&iAddrLen))) > 0)
{
VoipConnectionT *pConnection;
int32_t iConnID;
// extract client identifier
uint32_t uClientId = _VoipDecodeU32(RecvPacket.VoipPacketHead.aClientId);
// find the matching connection
for (iConnID = 0, pConnection = NULL; iConnID < pConnectionlist->iMaxConnections; iConnID++)
{
pConnection = &pConnectionlist->pConnections[iConnID];
// if the clientID matches, this is the connection
if (uClientId == pConnection->uRemoteClientId)
{
break;
}
}
// if we found a matching connection
if (iConnID < pConnectionlist->iMaxConnections)
{
// give the data to the connection
_VoipConnectionRecvSingle(pConnectionlist, pConnection, &RecvAddr, &RecvPacket, iRecvSize);
}
else if (!VOIP_SamePacketType(&RecvPacket.VoipPacketHead, &_Voip_ConnPacket) && !VOIP_SamePacketType(&RecvPacket.VoipPacketHead, &_Voip_DiscPacket))
{
NetPrintf(("voipconnection: ignoring 0x%02x%02x%02x packet from address %a:%d clientId=0x%08x\n",
RecvPacket.VoipPacketHead.aType[0], RecvPacket.VoipPacketHead.aType[1], RecvPacket.VoipPacketHead.aType[2],
SocketNtohl(RecvAddr.sin_addr.s_addr), SocketNtohs(RecvAddr.sin_port),
uClientId));
NetPrintMem(&RecvPacket, iRecvSize, "packet data");
}
}
// return data length
return(iRecvSize);
}
/*F********************************************************************************/
/*!
\Function _VoipConnectionRecvCallback
\Description
Voip socket recv event callback.
\Input *pSocket - voip socket
\Input iFlags - unused
\Input *pRef - connectionlist ref
\Output
int32_t - zero
\Version 12/16/2005 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _VoipConnectionRecvCallback(SocketT *pSocket, int32_t iFlags, void *pRef)
{
VoipConnectionlistT *pConnectionlist = (VoipConnectionlistT *)pRef;
// see if we have exclusive access
if (NetCritTry(&pConnectionlist->NetCrit))
{
// try and receive data
_VoipConnectionRecv(pConnectionlist);
// free access
NetCritLeave(&pConnectionlist->NetCrit);
}
else
{
NetPrintf(("voipconnection: _VoipConnectionRecvCallback() could not acquire connectionlist critical section\n"));
}
return(0);
}
/*F********************************************************************************/
/*!
\Function _VoipConnectionTrySocketClose
\Description
Check if the voip socket can be closed. The close will occur when all the
connections are closed. Moved from the voip loop, to also be done immediately
upon connection stop.
\Input *pConnectionlist - connectionlist ref
\Output
int32_t - zero
\Version 06/18/2009 (jrainy)
*/
/********************************************************************************F*/
static void _VoipConnectionTrySocketClose(VoipConnectionlistT *pConnectionlist)
{
int32_t iConnId, iNumConnections;
NetCritEnter(&pConnectionlist->NetCrit);
// count number of active connections
for (iConnId = 0, iNumConnections = 0; iConnId < pConnectionlist->iMaxConnections; iConnId++)
{
if (pConnectionlist->pConnections[iConnId].eState != ST_DISC)
{
iNumConnections++;
}
}
// if no connections left, kill socket
if ((iNumConnections == 0) && (pConnectionlist->pSocket != NULL))
{
NetPrintf(("voipconnection: closing socket in _VoipConnectionTrySocketClose()\n"));
SocketClose(pConnectionlist->pSocket);
pConnectionlist->pSocket = NULL;
pConnectionlist->uBindPort = 0;
}
NetCritLeave(&pConnectionlist->NetCrit);
}
/*F********************************************************************************/
/*!
\Function _VoipConnectionStop
\Description
Stop a connection.
\Input *pConnectionlist - connectionlist ref
\Input *pConnection - connection to stop
\Input iConnID - index of connection to stop, or VOIP_CONNID_ALL to stop all connections
\Input bSendDiscMsg - TRUE - send DISC msg to peer; FALSE - do not send DISC msg to peer
\Notes
Loss of the single unreliable disconnect packet sent in this function
is not critical, as the connection manager will respond to unexpected
packets that are not connect/disconnect packets (ie pings and voice
packets) with disconnection requests.
\Version 05/10/2004 (jbrookes)
*/
/********************************************************************************F*/
static void _VoipConnectionStop(VoipConnectionlistT *pConnectionlist, VoipConnectionT *pConnection, int32_t iConnID, int32_t bSendDiscMsg)
{
VoipCommonRefT *pVoipCommon = (VoipCommonRefT *)VoipGetRef();
int32_t i = 0;
NetCritEnter(&pConnectionlist->NetCrit);
// make sure we're not already disconnected
if (pConnection->eState == ST_DISC)
{
NetPrintf(("voipconnection: [%d] disconnection attempt canceled because connection already in state ST_DISC!\n", iConnID));
NetCritLeave(&pConnectionlist->NetCrit);
return;
}
if (bSendDiscMsg)
{
// send a disconnection packet
uint32_t uLocalClientId = pConnection->bIsLocalClientIdValid ? pConnection->uLocalClientId : pConnectionlist->uClientId;
_VoipConnectionDisc(iConnID, uLocalClientId, pConnection->uRemoteClientId, pConnection->uSessionId[0], &pConnection->SendAddr, pConnectionlist->pSocket);
}
else
{
NetPrintf(("voipconnection: [%d] no need to send disconnect message to %a:%d\n", iConnID,
SocketNtohl(pConnection->SendAddr.sin_addr.s_addr), SocketNtohs(pConnection->SendAddr.sin_port)));
}
// set to disconnected before unregister
pConnection->eState = ST_DISC;
pConnection->bConnPktRecv = FALSE;
// subtract connection identifier from send/recv masks
VoipConnectionSetSendMask(pConnectionlist, pConnectionlist->uSendMask & ~(1 << iConnID));
VoipConnectionSetRecvMask(pConnectionlist, pConnectionlist->uRecvMask & ~(1 << iConnID));
// unregister all the remote users on this connection with voipheadset
VoipConnectionRegisterRemoteTalkers(pConnectionlist, iConnID, FALSE);
// clear outbound reliable queue
while (pConnection->pOutboundReliableDataQueue != NULL)
{
_VoipConnectionReliableDataDequeue(pConnectionlist, iConnID);
}
// clear connection and mark as disconnected
ds_memclr(pConnection, sizeof(*pConnection));
pConnection->uRemoteConnStatus = VOIP_CONN_STOPPED;
pConnection->iVoipServerConnId = VOIP_CONNID_NONE;
for ( i = 0; i < VOIP_MAXLOCALUSERS; ++i)
{
pConnectionlist->uLocalUserStatus[i] &= ~VOIP_LOCAL_USER_SENDVOICE;
pConnectionlist->uLocalUserLastSend[i] = 0;
}
// clear channel config maintained by VoipCommon
ds_memclr(&pVoipCommon->uRemoteChannelSelection[iConnID], sizeof(pVoipCommon->uRemoteChannelSelection[iConnID]));
NetCritLeave(&pConnectionlist->NetCrit);
}
/*** Public functions *************************************************************/
/*F********************************************************************************/
/*!
\Function VoipConnectionStartup
\Description
Startup a connectionlist.
\Input *pConnectionlist - connectionlist to init
\Input iMaxPeers - max number of connections to support
\Output
int32_t - zero=success, negative=error
\Version 03/20/2004 (jbrookes)
*/
/********************************************************************************F*/
int32_t VoipConnectionStartup(VoipConnectionlistT *pConnectionlist, int32_t iMaxPeers)
{
int32_t iConnId;
int32_t iMemGroup;
void *pMemGroupUserData;
//Check to see if Max Peer has exceeded VOIP_MAXCONNECT
if (iMaxPeers > VOIP_MAXCONNECT)
{
NetPrintf(("voipconnection: error, max peer exceeded VOIP_MAXCONNECT.\n"));
return(-2);
}
// Query current mem group data
VoipCommonMemGroupQuery(&iMemGroup, &pMemGroupUserData);
// allocate and init connectionlist
if ((pConnectionlist->pConnections = (VoipConnectionT *)DirtyMemAlloc(sizeof(VoipConnectionT) * iMaxPeers, VOIP_MEMID, iMemGroup, pMemGroupUserData)) == NULL)
{
NetPrintf(("voipconnection: error, unable to allocate connectionlist\n"));
return(-1);
}
ds_memclr(pConnectionlist->pConnections, sizeof(VoipConnectionT) * iMaxPeers);
pConnectionlist->iFriendConnId = VOIP_CONNID_NONE;
// reset local-to-server conn id mappings
for (iConnId = 0; iConnId < iMaxPeers; iConnId++)
{
pConnectionlist->pConnections[iConnId].iVoipServerConnId = VOIP_CONNID_NONE;
}
// init critical section
NetCritInit(&pConnectionlist->NetCrit, "voipconnection");
// set default timeout
pConnectionlist->iDataTimeout = VOIP_TIMEOUT;
// set max peers
pConnectionlist->iMaxConnections = iMaxPeers;
NetPrintf(("voipconnection: max connections = %d max reliable data size = %d max ping payload size = %d\n",
pConnectionlist->iMaxConnections, VOIP_MAXRELIABLEDATA, VOIP_MAXPINGPKTSIZE));
return(0);
}
/*F********************************************************************************/
/*!
\Function VoipConnectionSetCallbacks
\Description
Set required recv voice and reg user callbacks.
\Input *pConnectionlist - connectionlist to register callbacks for
\Input *pVoiceCb - voice callback to register
\Input *pVoiceUserData - user data to be passed to the voice callback
\Input *pTextCb - text callback to register
\Input *pTextUserData - user data to be passed to the text callback
\Input *pRegUserCb - user reg callback to register
\Input *pRegUserUserData - user data to be passed to the reguser callback
\Input *pOpaqueCb - opaque data callback to register
\Input *pOpaqueUserData - user data to be passed to the opaquedata callback
\Version 03/20/2004 (jbrookes)
*/
/********************************************************************************F*/
void VoipConnectionSetCallbacks(VoipConnectionlistT *pConnectionlist, VoipConnectRecvVoiceCbT *pVoiceCb, void *pVoiceUserData,
VoipConnectRecvTextCbT *pTextCb, void *pTextUserData,
VoipConnectRegUserCbT *pRegUserCb, void *pRegUserUserData,
VoipConnectRecvOpaqueCbT *pOpaqueCb, void *pOpaqueUserData)
{
// save callbacks and callback user data
pConnectionlist->pRegUserCb = pRegUserCb;
pConnectionlist->pRegUserUserData = pRegUserUserData;
pConnectionlist->pVoiceCb = pVoiceCb;
pConnectionlist->pVoiceUserData = pVoiceUserData;
pConnectionlist->pTextCb = pTextCb;
pConnectionlist->pTextUserData = pTextUserData;
pConnectionlist->pOpaqueCb = pOpaqueCb;
pConnectionlist->pOpaqueUserData = pOpaqueUserData;
}
/*F********************************************************************************/
/*!
\Function VoipConnectionSetTextCallback
\Description
Set required text callbacks for X1 crossplay asscessibility
Should be set to null for native mode
\Input *pConnectionlist - connectionlist to register callbacks for
\Input *pTextCb - text callback to register
\Input *pTextUserData - user data to be passed to the text callback
\Version 04/10/2019 (tcho)
*/
/********************************************************************************F*/
void VoipConnectionSetTextCallback(VoipConnectionlistT *pConnectionlist, VoipConnectRecvTextCbT *pTextCb, void *pTextUserData)
{
// save callbacks and callback user data
pConnectionlist->pTextCb = pTextCb;
pConnectionlist->pTextUserData = pTextUserData;
}
/*F********************************************************************************/
/*!
\Function VoipConnectionShutdown
\Description
Shutdown a connectionlist
\Input *pConnectionlist - connectionlist to close
\Version 03/20/2004 (jbrookes)
*/
/********************************************************************************F*/
void VoipConnectionShutdown(VoipConnectionlistT *pConnectionlist)
{
int32_t iMemGroup;
void *pMemGroupUserData;
LinkedReliableDataT *pEntry;
// query current mem group data
VoipCommonMemGroupQuery(&iMemGroup, &pMemGroupUserData);
// clear free pool of reliable data entries
while ((pEntry = _VoipGetReliableDataBufferFromFreePool(pConnectionlist, FALSE)) != NULL)
{
DirtyMemFree(pEntry, VOIP_MEMID, iMemGroup, pMemGroupUserData);
}
// dispose of connectionlist
DirtyMemFree(pConnectionlist->pConnections, VOIP_MEMID, iMemGroup, pMemGroupUserData);
// dispose of socket
if (pConnectionlist->pSocket)
{
NetPrintf(("voipconnection: closing socket in VoipConnectionShutdown()\n"));
SocketClose(pConnectionlist->pSocket);
}
// release critical section
NetCritKill(&pConnectionlist->NetCrit);
// clear connectionlist
ds_memclr(pConnectionlist, sizeof(*pConnectionlist));
}
/*F********************************************************************************/
/*!
\Function VoipConnectionCanAllocate
\Description
Check whether a given Connection ID can be allocated in this Connection List
\Input *pConnectionlist - connection list ref
\Input iConnID - connection index
\Output
uint8_t - Whether a connection can be allocated with given ConnID
\Version 02/19/2008 (jrainy)
*/
/********************************************************************************F*/
uint8_t VoipConnectionCanAllocate(VoipConnectionlistT *pConnectionlist, int32_t iConnID)
{
if ((iConnID < 0) || (iConnID >= pConnectionlist->iMaxConnections))
{
return(FALSE);
}
return(pConnectionlist->pConnections[iConnID].eState == ST_DISC);
}
/*F********************************************************************************/
/*!
\Function VoipConnectionStart
\Description
Start a connection to a peer.
\Input *pConnectionlist - connection list ref
\Input iConnID - connection index
\Input uAddr - address to connect to
\Input uConnPort - connection port
\Input uBindPort - local bind port
\Input uClientId - id of client to connect to
\Input uSessionId - session id (cannot be 0)
\Output
int32_t - connection identifier on success, negative=failure
\Version 03/18/2004 (jbrookes)
*/
/********************************************************************************F*/
int32_t VoipConnectionStart(VoipConnectionlistT *pConnectionlist, int32_t iConnID, uint32_t uAddr, uint32_t uConnPort, uint32_t uBindPort, uint32_t uClientId, uint32_t uSessionId)
{
VoipConnectionT *pConnection;
struct sockaddr BindAddr;
int32_t iUserIndex;
// sanity check: make sure local and remote clientIDs are different
if (pConnectionlist->uClientId == uClientId)
{
NetPrintf(("voipconnection: local client id (%d) and remote client id (%d) can't be same\n", pConnectionlist->uClientId, uClientId));
return(-1);
}
// if we haven't allocated a socket yet, do so now
if (pConnectionlist->pSocket == NULL)
{
int32_t iResult;
// open the socket
if ((pConnectionlist->pSocket = SocketOpen(AF_INET, SOCK_DGRAM, VOIP_IPPROTO)) == NULL)
{
NetPrintf(("voipconnection: error creating socket\n"));
return(-2);
}
// bind the socket
SockaddrInit(&BindAddr, AF_INET);
SockaddrInSetPort(&BindAddr, uBindPort);
if ((iResult = SocketBind(pConnectionlist->pSocket, &BindAddr, sizeof(BindAddr))) != SOCKERR_NONE)
{
NetPrintf(("voipconnection: error %d binding socket to port %d, trying random\n", iResult, uBindPort));
SockaddrInSetPort(&BindAddr, 0);
if ((iResult = SocketBind(pConnectionlist->pSocket, &BindAddr, sizeof(BindAddr))) != SOCKERR_NONE)
{
NetPrintf(("voipconnection: error %d binding socket\n", iResult));
SocketClose(pConnectionlist->pSocket);
pConnectionlist->pSocket = NULL;
return(-3);
}
}
// retrieve bound port
SocketInfo(pConnectionlist->pSocket, 'bind', 0, &BindAddr, sizeof(BindAddr));
uBindPort = SockaddrInGetPort(&BindAddr);
NetPrintf(("voipconnection: bound socket to port %d\n", uBindPort));
// save local port
pConnectionlist->uBindPort = uBindPort;
// setup for socket events
SocketCallback(pConnectionlist->pSocket, CALLB_RECV, 5000, pConnectionlist, &_VoipConnectionRecvCallback);
}
// make sure bind matches our previous bind
if (uBindPort != pConnectionlist->uBindPort)
{
NetPrintf(("voipconnection: warning, only one global bind port is currently supported, using previously specified port\n"));
}
// convert address and ports to network form
uAddr = SocketHtonl(uAddr);
uConnPort = SocketHtons(uConnPort);
/*
Guarding the following code with NetCrit proved to be required to protect against
_VoipConnectionStop() being invoked from the socket async recv thread upon reception
of a DISC packet (typically belonging to a previous session) on that connection.
We did not include the above portion of this function within this critical
usage function, because atomic access to it is guaranteed by the threadcrit
being lock externally.
*/
NetCritEnter(&pConnectionlist->NetCrit);
// allocate a connection
if ((pConnection = _VoipConnectionAllocate(pConnectionlist, iConnID)) == NULL)
{
NetCritLeave(&pConnectionlist->NetCrit);
NetPrintf(("voipconnection: alloc failed\n"));
return(-4);
}
iConnID = VOIP_ConnID(pConnectionlist, pConnection);
// init the connection
ds_memclr(pConnection, sizeof(*pConnection));
pConnection->iVoipServerConnId = VOIP_CONNID_NONE;
pConnection->SendAddr.sin_family = AF_INET;
pConnection->SendAddr.sin_addr.s_addr = uAddr;
pConnection->SendAddr.sin_port = uConnPort;
pConnection->uRemoteClientId = uClientId;
// sequence number for reliable data ranges from 1 to 127
pConnection->uOutSeqNb = 1; // use 1 as the first outbound sequence number
pConnection->uInSeqNb = 0; // seq nb of last received inbound message. validity range : [1,127]. 0 means no inbound traffic received yet
// add specified session ID to the set of sessions sharing this connection
VoipConnectionAddSessionId(pConnectionlist, iConnID, uSessionId);
pConnection->uRecvSeqn = 0xFFFFFFFF; // intialize last receive sequence number with -1.
pConnection->uRemoteConnStatus = 0;
for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS; ++iUserIndex)
{
pConnection->uRemoteUserStatus[iUserIndex] = 0;
}
pConnection->eState = ST_CONN;
pConnection->uLastRecv = NetTick();
// set up to transmit mic data
for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS_EXTENDED; iUserIndex++)
{
ds_memcpy_s(&pConnection->VoipMicrPacket[iUserIndex].Head, sizeof(pConnection->VoipMicrPacket[iUserIndex].Head), &_Voip_MicrPacket, sizeof(_Voip_MicrPacket));
}
// output connect message
NetPrintf(("voipconnection: [%d] connecting to %a:%d localId=0x%08x remoteId=0x%08x\n", iConnID,
SocketNtohl(pConnection->SendAddr.sin_addr.s_addr), SocketNtohs(pConnection->SendAddr.sin_port),
pConnection->uLocalClientId, pConnection->uRemoteClientId));
NetCritLeave(&pConnectionlist->NetCrit);
// return connection ID to caller
return(iConnID);
}
/*F********************************************************************************/
/*!
\Function VoipConnectionUpdate
\Description
Update all connections
\Input *pConnectionlist - connection list
\Version 03/18/2004 (jbrookes)
*/
/********************************************************************************F*/
void VoipConnectionUpdate(VoipConnectionlistT *pConnectionlist)
{
int32_t iConnId;
uint32_t uTick;
// get sole access
NetCritEnter(&pConnectionlist->NetCrit);
// receive any incoming data
while(_VoipConnectionRecv(pConnectionlist) > 0)
;
_VoipConnectionTrySocketClose(pConnectionlist);
// relinquish sole access
NetCritLeave(&pConnectionlist->NetCrit);
// update connection status for all connections, keeping track of who we are receiving voice from
for (iConnId = 0, uTick = NetTick(), pConnectionlist->uRecvVoice = 0; iConnId < pConnectionlist->iMaxConnections; iConnId++)
{
pConnectionlist->uRecvVoice |= _VoipConnectionUpdateSingle(pConnectionlist, &pConnectionlist->pConnections[iConnId], iConnId, uTick);
}
#if !defined(DIRTYCODE_XBOXONE) && !defined(DIRTYCODE_GDK)
/* check if we need to time out talking status
on xbox one this is handled in the voipxbxone _VoipUpdateLocalStatus()
*/
for (int32_t i = 0; i < VOIP_MAXLOCALUSERS; ++i)
{
if ((pConnectionlist->uLocalUserStatus[i] & VOIP_LOCAL_USER_TALKING) && (NetTickDiff(uTick, pConnectionlist->uLastVoiceTime[i]) > VOIP_TALKTIMEOUT))
{
pConnectionlist->uLocalUserStatus[i] &= ~VOIP_LOCAL_USER_TALKING;
}
}
#endif
}
/*F********************************************************************************/
/*!
\Function VoipConnectionStop
\Description
Stop a connection with a peer.
\Input *pConnectionlist - connection list ref
\Input iConnID - connection to stop, or VOIP_CONNID_ALL to stop all connections
\Input bSendDiscMsg - TRUE - send DISC msg to peer; FALSE - do not send DISC msg to peer
\Version 03/18/2004 (jbrookes)
*/
/********************************************************************************F*/
void VoipConnectionStop(VoipConnectionlistT *pConnectionlist, int32_t iConnID, int32_t bSendDiscMsg)
{
if (iConnID == VOIP_CONNID_ALL)
{
// disconnect from all current connections
for (iConnID = 0; iConnID < pConnectionlist->iMaxConnections; iConnID++)
{
_VoipConnectionStop(pConnectionlist, &pConnectionlist->pConnections[iConnID], iConnID, bSendDiscMsg);
}
}
else if ((iConnID >= 0) && (iConnID < pConnectionlist->iMaxConnections))
{
// disconnect from the given connection
_VoipConnectionStop(pConnectionlist, &pConnectionlist->pConnections[iConnID], iConnID, bSendDiscMsg);
}
else
{
NetPrintf(("voipconnection: disconnect with iConnID=%d is invalid\n", iConnID));
}
_VoipConnectionTrySocketClose(pConnectionlist);
}
/*F********************************************************************************/
/*!
\Function VoipConnectionSend
\Description
Send data to peer.
\Input *pConnectionlist - connectionlist to send to
\Input uSendMask - mask of connections to send to
\Input *pVoiceData - pointer to data to send
\Input iDataSize - size of data to send
\Input *pMetaData - pointer to metadata to be added to voip packet
\Input iMetaDataSize - size of metadata
\Input uUserIndex - local user index
\Input uSendSeqn - seq nb
\Version 03/17/2004 (jbrookes)
*/
/********************************************************************************F*/
void VoipConnectionSend(VoipConnectionlistT *pConnectionlist, uint32_t uSendMask, const uint8_t *pVoiceData, int32_t iDataSize, const uint8_t *pMetaData, int32_t iMetaDataSize, uint32_t uUserIndex, uint8_t uSendSeqn)
{
uint32_t uCurTick = NetTick();
int32_t iConnID;
int32_t iParticipatingUserIndex;
uint8_t bSentToVoipServer = FALSE;
// early exit if metadata is too big
if ((pMetaData != NULL) && (iMetaDataSize > 256))
{
NetPrintf(("voipconnection: critical error! metadata is too big\n"));
return;
}
// early exit if the user index is invalid
if (uUserIndex >= VOIP_MAXLOCALUSERS_EXTENDED)
{
return;
}
// if we are dealing with the remote user update all the user's last voice receive time
if (uUserIndex == VOIP_SHARED_USER_INDEX)
{
for (iParticipatingUserIndex = 0; iParticipatingUserIndex < VOIP_MAXLOCALUSERS; ++iParticipatingUserIndex)
{
if (pConnectionlist->aIsParticipating[iParticipatingUserIndex] == TRUE)
{
pConnectionlist->uLastVoiceTime[iParticipatingUserIndex] = uCurTick;
}
}
}
else
{
pConnectionlist->uLastVoiceTime[uUserIndex] = uCurTick;
}
// loop through all connections
for (iConnID = 0; iConnID < pConnectionlist->iMaxConnections; iConnID++)
{
// ref the connection
VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnID];
// skip connections that are not set in the sendmask parameter
if ((uSendMask & (1 << iConnID)) == 0)
{
continue;
}
// for connections via voip server, send only to the first connection
// todo: amakoukji, for CCS phase 2 we will need to remember what voip server we already sent the packet to and skip
if ((pConnection->iVoipServerConnId != VOIP_CONNID_NONE) && (bSentToVoipServer != FALSE))
{
continue;
}
/* is the connection active, and are we sending to it?
xboxone: After rebasing VoipHeadsetXboxOne to MS game chat 2, per-connection muting
is handled by VoipHeadsetXboxOne (See usage of +snd, -snd, +rcv, -rcv).
But for crossplay we want the muting to be applied here just like other plaforms.
Testing has shown that apply te send mask and +snd, -snd, +rcv, -rcv has no negative effect.*/
if ((pConnection->eState == ST_ACTV) && (pConnectionlist->uSendMask & (1 << iConnID)))
{
uint8_t *pWrite; // write position in voip packet
VoipMicrPacketT *pMicrPacket = &pConnection->VoipMicrPacket[uUserIndex];
uint8_t bVariableSubPktLen;
int32_t iResult;
// set talking flag
// if the voice packet is from a shared user set all the participating user status to be talking
if (uUserIndex == VOIP_SHARED_USER_INDEX)
{
for (iParticipatingUserIndex = 0; iParticipatingUserIndex < VOIP_MAXLOCALUSERS; ++iParticipatingUserIndex)
{
if (pConnectionlist->aIsParticipating[iParticipatingUserIndex] == TRUE)
{
pConnectionlist->uLocalUserStatus[iParticipatingUserIndex] |= VOIP_LOCAL_USER_TALKING;
}
}
}
else
{
pConnectionlist->uLocalUserStatus[uUserIndex] |= VOIP_LOCAL_USER_TALKING;
}
// record maximum outgoing sub-pkt size on this connection
if (iDataSize > pConnection->iMaxSubPktSize)
{
NetPrintf(("voipconnection: [%d] max recorded outbound sub-pkt size increased from %d to %d\n", iConnID, pConnection->iMaxSubPktSize, iDataSize));
pConnection->iMaxSubPktSize = iDataSize;
}
// is new metadata different than metadata already in packet
if ((pMicrPacket->MicrInfo.uNumSubPackets != 0) && (pMetaData != NULL))
{
uint8_t bDifferentMetaData = FALSE;
uint8_t *pPackedMetaData = _VoipPacketGetMetaDataPtr(pMicrPacket);
#if DIRTYCODE_LOGGING
if (!(pMicrPacket->Head.uFlags & VOIP_PACKET_STATUS_FLAG_METADATA))
{
NetPrintf(("voipconnection: [%d] critical error - metadata expected in packet but missing\n", iConnID));
}
#endif
if (iMetaDataSize == *pPackedMetaData)
{
if (memcmp(pMetaData, (pPackedMetaData+1), iMetaDataSize) != 0)
{
bDifferentMetaData = TRUE;
}
}
else
{
bDifferentMetaData = TRUE;
}
if (bDifferentMetaData)
{
NetPrintf(("voipconnection: [%d] flushing pending voip packet because new sub-packet has different metadata\n", iConnID));
_VoipConnectionSendSingle(pConnectionlist, pConnection, uUserIndex, uCurTick, TRUE);
}
}
// if no voice sub-packets are queued yet
if (pMicrPacket->MicrInfo.uNumSubPackets == 0)
{
// if needed, add reliable protocol components to this voip packet
if (pConnection->iVoipServerConnId != VOIP_CONNID_NONE)
{
// packet routed through voip server
pWrite = _VoipReliableDataOutProcess(pConnectionlist, VOIP_CONNID_ALL, &pMicrPacket->Head, &pMicrPacket->aData[0], sizeof(pMicrPacket->aData));
}
else
{
// p2p packet
pWrite = _VoipReliableDataOutProcess(pConnectionlist, iConnID, &pMicrPacket->Head, &pMicrPacket->aData[0], sizeof(pMicrPacket->aData));
}
// if the voice timer has elapsed, reset it
if (NetTickDiff(uCurTick, pConnection->aVoiceSendTimer[uUserIndex]) >= 0)
{
pConnection->aVoiceSendTimer[uUserIndex] = uCurTick + VOIP_MSPERPACKET;
}
// if necessary, add metadata before we start appending the first packet
if (pMetaData != NULL)
{
// store metadata length in packet buffer
*pWrite++ = (unsigned)iMetaDataSize;
// copy metadata into packet buffer
ds_memcpy(pWrite, pMetaData, iMetaDataSize);
pWrite += iMetaDataSize;
// set flag signaling that payload starts with metadata size/metadata pair
pMicrPacket->Head.uFlags |= VOIP_PACKET_STATUS_FLAG_METADATA;
}
}
else
{
pWrite = _VoipPacketGetWritePtr(pMicrPacket);
}
// does this platform work with variable length sub-pkts
if ((iResult = VoipStatus(VoipGetRef(), 'vlen', 0, &bVariableSubPktLen, sizeof(bVariableSubPktLen))) < 0)
{
// if selector is not supported, assume fixed length
bVariableSubPktLen = FALSE;
}
if (bVariableSubPktLen)
{
// MicrInfo.uSubPacketSize == 0xFF means that sub-pkts may have different sizes
pMicrPacket->MicrInfo.uSubPacketSize = 0xFF;
}
else
{
#if DIRTYCODE_LOGGING
if (pMicrPacket->MicrInfo.uSubPacketSize != 0 && pMicrPacket->MicrInfo.uSubPacketSize != iDataSize)
{
NetPrintf(("voipconnection: [%d] critical error - sub-packets have different size -> %d vs %d\n", iConnID, pMicrPacket->MicrInfo.uSubPacketSize, iDataSize));
}
if (pMicrPacket->MicrInfo.uSubPacketSize == 0xFF)
{
NetPrintf(("voipconnection: [%d] critical error - sub-pkt size conflicting with fixed-length sub-pkt mode\n", iConnID));
}
#endif
pMicrPacket->MicrInfo.uSubPacketSize = (unsigned)iDataSize;
}
if (bVariableSubPktLen)
{
// store sub-pkt length in packet buffer
*pWrite = (unsigned)iDataSize;
// copy data into packet buffer
ds_memcpy(pWrite+1, pVoiceData, iDataSize);
#if DIRTYCODE_LOGGING
if ((pWrite + 1 + iDataSize) >= ((&pConnection->VoipMicrPacket[uUserIndex].aData[0]) + sizeof(pConnection->VoipMicrPacket[uUserIndex].aData)))
{
NetPrintf(("voipconnection: [%d] critical error - sub-packet packing overflowed!\n", iConnID));
}
#endif
}
else
{
// copy data into packet buffer
ds_memcpy(pWrite, pVoiceData, iDataSize);
}
// increment sub-packet count
pMicrPacket->MicrInfo.uNumSubPackets += 1;
// identify local user index that generated the voice data
pMicrPacket->MicrInfo.uUserIndex = uUserIndex;
// see if we need to send any buffered voice data
_VoipConnectionSendSingle(pConnectionlist, pConnection, uUserIndex, uCurTick, FALSE);
if (pConnection->iVoipServerConnId != VOIP_CONNID_NONE)
{
// mark game server as served
bSentToVoipServer = TRUE;
}
}
}
}
/*F********************************************************************************/
/*!
\Function VoipConnectionFlush
\Description
Send currently queued voice data, if any.
\Input *pConnectionlist - connectionlist to send to
\Input iConnID - connection ident of connection to flush
\Output
int32_t - number of voice packets flushed
\Notes
Currently only useful on Xenon
\Version 01/04/2006 (jbrookes)
*/
/********************************************************************************F*/
int32_t VoipConnectionFlush(VoipConnectionlistT *pConnectionlist, int32_t iConnID)
{
int32_t iUserIndex;
int32_t iNumSubPackets = 0;
int32_t iTotalNumSubPackets = 0;
// ref the connection
VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnID];
for (iUserIndex = 0; iUserIndex < VOIP_MAXLOCALUSERS_EXTENDED; iUserIndex++)
{
iNumSubPackets = (signed)pConnection->VoipMicrPacket[iUserIndex].MicrInfo.uNumSubPackets;
/* is the connection active, and are we sending to it?
xboxone: Never "mute" here, i.e. skip applying pConnectionlist->uSendMask.
Per-connection muting handling is rather deferred to VoipHeadsetXboxOne (See usage of +snd, -snd, +rcv, -rcv).
After rebasing VoipHeadsetXboxOne to MS game chat 2, we found out that the integration
with our per-connection muting here was not behaving properly during unmuting: resuming
submission of data frames to MS game chat 2 resulted in a ~ 5-sec delay before speech
would resume. */
if ((pConnection->eState == ST_ACTV) && (iNumSubPackets > 0)
#if !defined(DIRTYCODE_XBOXONE) && !defined(DIRTYCODE_GDK)
&& (pConnectionlist->uSendMask & (1 << iConnID))
#endif
)
{
// see if we need to send any buffered voice data
_VoipConnectionSendSingle(pConnectionlist, pConnection, iUserIndex, NetTick(), TRUE);
// increment total number of sub-packets flushed for the connection
iTotalNumSubPackets += iNumSubPackets;
}
}
// return total number of sub-packets flushed for the connection
return(iTotalNumSubPackets);
}
/*F********************************************************************************/
/*!
\Function VoipConnectionRegisterRemoteTalkers
\Description
Register/unregister remote users associated with the given connection.
\Input *pConnectionlist - connectionlist to send to
\Input iConnID - connection ident of connection to flush
\Input bRegister - if TRUE register talkers, else unregister them
\Version 01/04/2006 (jbrookes)
*/
/********************************************************************************F*/
void VoipConnectionRegisterRemoteTalkers(VoipConnectionlistT *pConnectionlist, int32_t iConnID, uint32_t bRegister)
{
VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnID];
int32_t iUser;
// register any remote users that are part of the connection
for (iUser = 0; iUser < pConnection->iMaxRemoteUsersExt; iUser++)
{
VoipUserT* pRemoteUser = (VoipUserT *)(&pConnection->RemoteUsers[iUser]);
// if no user, don't register
if (VOIP_NullUser(pRemoteUser))
{
continue;
}
#if DIRTYCODE_LOGGING
// if the user shouldn't be joining us, complain
if (pRemoteUser->ePlatform != VOIP_LOCAL_PLATFORM) // a platform that doesn't match the local platform is joining us
{
// is cross play setup properly for the remote user
if ((pRemoteUser->uFlags & VOIPUSER_FLAG_CROSSPLAY) == FALSE)
{
NetPrintf(("voipconnection: [%d] error, platform %d, persona %lld attempted to join, but cross play is not enabled remotely.\n", iConnID, pRemoteUser->ePlatform, pRemoteUser->AccountInfo.iPersonaId));
}
// is cross play setup properly for the local user
if (VoipStatus(VoipGetRef(), 'xply', 0, NULL, 0) == FALSE)
{
NetPrintf(("voipconnection: [%d] error, platform %d, persona %lld attempted to join, but cross play is not enabled locally.\n", iConnID, pRemoteUser->ePlatform, pRemoteUser->AccountInfo.iPersonaId));
}
}
else if (((pRemoteUser->uFlags & VOIPUSER_FLAG_CROSSPLAY) == TRUE) != (VoipStatus(VoipGetRef(), 'xply', 0, NULL, 0) == TRUE)) // the platform matches, but the cross play settings should match too
{
NetPrintf(("voipconnection: [%d] error, local and remote user have mismatched cross play settings, platform %d, persona %lld.\n", iConnID, pRemoteUser->ePlatform, pRemoteUser->AccountInfo.iPersonaId));
}
#endif
// register the user
pConnectionlist->pRegUserCb((VoipUserT *)&pConnection->RemoteUsers[iUser], iConnID, bRegister, pConnectionlist->pRegUserUserData);
#if defined(DIRTYCODE_XBOXONE) || defined(DIRTYCODE_GDK)
// reapply player-to-player xone comm relationships
pConnectionlist->bApplyRelFromMuting = TRUE;
#endif
}
// set mute list to be updated
pConnectionlist->bUpdateMute = TRUE;
}
/*F********************************************************************************/
/*!
\Function VoipConnectionSetSendMask
\Description
Set connections to send to.
\Input *pConnectionlist - connectionlist to send to
\Input uSendMask - connection send mask
\Version 03/22/2004 (jbrookes)
*/
/********************************************************************************F*/
void VoipConnectionSetSendMask(VoipConnectionlistT *pConnectionlist, uint32_t uSendMask)
{
if (pConnectionlist->uSendMask != uSendMask)
{
VoipCommonSetMask(&pConnectionlist->uSendMask, uSendMask, "sendmask");
}
}
/*F********************************************************************************/
/*!
\Function VoipConnectionSetRecvMask
\Description
Set connections to receive from.
\Input *pConnectionlist - connectionlist to send to
\Input uRecvMask - connection receive mask
\Version 03/22/2004 (jbrookes)
*/
/********************************************************************************F*/
void VoipConnectionSetRecvMask(VoipConnectionlistT *pConnectionlist, uint32_t uRecvMask)
{
if (pConnectionlist->uRecvMask != uRecvMask)
{
VoipCommonSetMask(&pConnectionlist->uRecvMask, uRecvMask, "recvmask");
}
}
/*F********************************************************************************/
/*!
\Function VoipConnectionAddSessionId
\Description
Add a session ID the set of higher level sessions sharing the specified VoIP connection.
\Input *pConnectionlist - connectionlist
\Input iConnId - connection ID
\Input uSessionId - session ID to be added
\Output
int32_t - 0 for success, negative for failure
\Notes
Maintaning a set of session IDs per voip connection is required to support cases
where two consoles are involved in a P2P game and a pg simultaneously. Both of these
constructs will have a different session ID over the same shared voip connection.
Under some specific race conditions affecting the order at which Blaze messages are
processed by each game client, it is very possible that the pg session id is setup
first on one side and second on the other side thus leading to VOIP connectivity
failures if the voip connection construct is not supporting multiple concurrent
session IDs.
\Version 08/30/2011 (mclouatre)
*/
/********************************************************************************F*/
int32_t VoipConnectionAddSessionId(VoipConnectionlistT *pConnectionlist, int32_t iConnId, uint32_t uSessionId)
{
int32_t iSessionIndex;
int32_t iRetCode = -1;
VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnId];
NetCritEnter(&pConnectionlist->NetCrit);
for (iSessionIndex = 0; iSessionIndex < VOIP_MAXSESSIONIDS; iSessionIndex++)
{
if (pConnection->uSessionId[iSessionIndex] == 0)
{
// free spot, insert session ID here
pConnection->uSessionId[iSessionIndex] = uSessionId;
NetPrintf(("voipconnection: [%d] added 0x%08x to set of sessions sharing this voip connection\n", iConnId, uSessionId));
iRetCode = 0;
break;
}
}
if (iRetCode != 0)
{
NetPrintf(("voipconnection: [%d] warning - 0x%08x could not be added to set of sessions sharing this voip connection because the list is full.\n", iConnId, uSessionId));
}
#if DIRTYCODE_LOGGING
// print set of sessions sharing this connection
_VoipConnectionPrintSessionIds(pConnectionlist, iConnId);
#endif
NetCritLeave(&pConnectionlist->NetCrit);
return(iRetCode);
}
/*F********************************************************************************/
/*!
\Function VoipConnectionDeleteSessionId
\Description
Delete a session ID from the set of sessions sharing this voip connection.
\Input *pConnectionlist - connectionlist
\Input iConnId - connection id
\Input uSessionId - session ID to be deleted
\Output
int32_t - 0 for success, negative for failure
\Version 08/30/2011 (mclouatre)
*/
/********************************************************************************F*/
int32_t VoipConnectionDeleteSessionId(VoipConnectionlistT *pConnectionlist, int32_t iConnId, uint32_t uSessionId)
{
int32_t iSessionIndex;
int32_t iRetCode = -1;
VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnId];
NetCritEnter(&pConnectionlist->NetCrit);
// now delete this address from the list of fallbacks
for(iSessionIndex = 0; iSessionIndex < VOIP_MAXSESSIONIDS; iSessionIndex++)
{
if (pConnection->uSessionId[iSessionIndex] == uSessionId)
{
int32_t iSessionIndex2;
// move all following session IDs one cell backward in the array
for(iSessionIndex2 = iSessionIndex; iSessionIndex2 < VOIP_MAXSESSIONIDS; iSessionIndex2++)
{
if (iSessionIndex2 == VOIP_MAXSESSIONIDS-1)
{
// last entry, reset to 0
pConnection->uSessionId[iSessionIndex2] = 0;
}
else
{
pConnection->uSessionId[iSessionIndex2] = pConnection->uSessionId[iSessionIndex2+1];
}
}
NetPrintf(("voipconnection: [%d] removed 0x%08x from set of sessions sharing this voip connection\n", iConnId, uSessionId));
iRetCode = 0;
break;
}
}
if (iRetCode != 0)
{
NetPrintf(("voipconnection: [%d] warning - 0x%08x not deleted because not found in set of sessions sharing this voip connection\n", iConnId, uSessionId));
}
#if DIRTYCODE_LOGGING
// print set of sessions sharing this connection
_VoipConnectionPrintSessionIds(pConnectionlist, iConnId);
#endif
NetCritLeave(&pConnectionlist->NetCrit);
return(iRetCode);
}
/*F********************************************************************************/
/*!
\Function VoipConnectionReliableBroadcastUser
\Description
Use this function to broadcast local user join-in-progress/leave-in-progress
data that needs to be sent reliably on all active connections (or connections in ST_CONN state).
\Input *pConnectionlist - connectionlist
\Input uLocalUserIndex - local user index
\Input bParticipating - TRUE if user joined-in-progress, FALSE if user left-in-progress
\Version 09/18/2014 (mclouatre)
*/
/********************************************************************************F*/
void VoipConnectionReliableBroadcastUser(VoipConnectionlistT *pConnectionlist, uint8_t uLocalUserIndex, uint32_t bParticipating)
{
ReliableDataT ReliableData;
uint8_t *pWrite = &ReliableData.aData[0];
// fill type
ReliableData.info.uType = (bParticipating ? VOIP_RELIABLE_TYPE_USERADD : VOIP_RELIABLE_TYPE_USERREM);
// fill payload with user index first
*pWrite++ = uLocalUserIndex;
// then append voip user
ds_memcpy_s(pWrite, sizeof(ReliableData.aData), &pConnectionlist->LocalUsers[uLocalUserIndex], sizeof(pConnectionlist->LocalUsers[uLocalUserIndex]));
pWrite = pWrite + sizeof(pConnectionlist->LocalUsers[uLocalUserIndex]);
// fill size
_VoipEncodeU16(&ReliableData.info.uSize[0], (pWrite - &ReliableData.aData[0]));
// enqueue for transmission on all connections (will also fill in seq number)
_VoipConnectionReliableDataEnqueue(pConnectionlist, VOIP_CONNID_ALL, &ReliableData);
}
/*F********************************************************************************/
/*!
\Function VoipConnectionReliableTranscribedTextMessage
\Description
Use this function to reliably broadcast a transcribed text message
(originated from a local user) on all connections that requested for it.
\Input *pConnectionlist - connectionlist
\Input uLocalUserIndex - local user index
\Input *pStrUtf8 - pointer to text message to be sent
\Version 09/18/2014 (mclouatre)
*/
/********************************************************************************F*/
void VoipConnectionReliableTranscribedTextMessage(VoipConnectionlistT *pConnectionlist, uint8_t uLocalUserIndex, const char *pStrUtf8)
{
int32_t iConnectionIndex;
ReliableDataT ReliableData;
uint8_t *pWrite = &ReliableData.aData[0];
// fill type
ReliableData.info.uType = VOIP_RELIABLE_TYPE_TEXT;
// fill payload with user index first
*pWrite++ = uLocalUserIndex;
ds_strnzcpy((char *)pWrite, pStrUtf8, VOIP_MAXRELIABLEDATA - 1);
pWrite += strlen((const char *)pWrite)+1;
// fill size of payload
_VoipEncodeU16(&ReliableData.info.uSize[0], (pWrite - &ReliableData.aData[0]));
// enqueue for transmission only on connections with users that requested for transcribed text
for (iConnectionIndex = 0; iConnectionIndex < pConnectionlist->iMaxConnections; iConnectionIndex++)
{
VoipConnectionT *pConnection = &pConnectionlist->pConnections[iConnectionIndex];
if ((pConnection->eState == ST_ACTV) && pConnection->bTranscribedTextRequested && (pConnectionlist->uSendMask & (1 << iConnectionIndex)))
{
_VoipConnectionReliableDataEnqueue(pConnectionlist, iConnectionIndex, &ReliableData);
}
}
}
/*F********************************************************************************/
/*!
\Function VoipConnectionReliableSendOpaque
\Description
Use this function to send opaque data reliably over specified connection.
\Input *pConnectionlist - connectionlist
\Input iConnID - connection to send the data over
\Input *pData - pointer to buffer filled with reliable data to be broadcasted
\Input uDataSize - reliable data size (in bytes)
\Version 09/18/2014 (mclouatre)
*/
/********************************************************************************F*/
void VoipConnectionReliableSendOpaque(VoipConnectionlistT *pConnectionlist, int32_t iConnID, const uint8_t *pData, uint16_t uDataSize)
{
ReliableDataT ReliableData;
// fill type
ReliableData.info.uType = VOIP_RELIABLE_TYPE_OPAQUE;
// fill size
_VoipEncodeU16(&ReliableData.info.uSize[0], uDataSize);
// fill payload
ds_memcpy_s(&ReliableData.aData[0], sizeof(ReliableData.aData), pData, uDataSize);
// enqueue for transmission on all connections (will also fill in seq number)
_VoipConnectionReliableDataEnqueue(pConnectionlist, iConnID, &ReliableData);
}