mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
969 lines
34 KiB
C++
969 lines
34 KiB
C++
//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
|
//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
|
//// PARTICULAR PURPOSE.
|
|
////
|
|
//// Copyright (c) Microsoft Corporation. All rights reserved
|
|
#include "pch.h"
|
|
#include "MeshPacketManager.h"
|
|
#include "Utils.h"
|
|
#include "MeshManager.h"
|
|
|
|
using namespace Concurrency;
|
|
|
|
namespace Microsoft {
|
|
namespace Xbox {
|
|
namespace Samples {
|
|
namespace NetworkMesh {
|
|
|
|
MeshPacketManager::MeshPacketManager(
|
|
uint8 localConsoleId,
|
|
unsigned short transportLevelPortNumber,
|
|
MeshManager^ meshManager,
|
|
bool dropOutOfOrderPackets ) :
|
|
m_localConsoleId( localConsoleId ),
|
|
m_debugTimeSincePacketReceive( 0.0f ),
|
|
m_debugTimeSincePacketSend( 0.0f ),
|
|
m_debugInsideWSAReceive( false ),
|
|
m_debugInsideWSASend( false ),
|
|
m_packetMessageId ( 0 ),
|
|
m_previousPacketMessageId(0),
|
|
m_dropOutOfOrderPackets(dropOutOfOrderPackets),
|
|
m_heartbeatMessageSize(DEFAULT_HEARTBEAT_SIZE)
|
|
{
|
|
// Note: this library requires the NetworkConnectivityLevel to be one of the following:
|
|
// XboxLiveAccess
|
|
// InternetAccess
|
|
// LocalAccess
|
|
// Depending on what the parent application is doing, only certain connectivty levels will work.
|
|
// This is clearly a matter for the parent application, so enforcement or lack thereof is best
|
|
// left to the parent, rather than down here in the library.
|
|
|
|
m_meshManager = Platform::WeakReference(meshManager);
|
|
m_meshPacketStatistics = ref new MeshPacketStatistics();
|
|
|
|
memset(m_bufferForWSARecv, 0, sizeof(WSARECV_BUFFER_SIZE));
|
|
|
|
WSADATA wsadata;
|
|
int result = WSAStartup( MAKEWORD( 2, 2 ), &wsadata );
|
|
if( result != 0 )
|
|
{
|
|
LogMeshPacketManagerComment( L"InitializeNetworkLayer failed with WSAError" );
|
|
throw ref new Platform::COMException( HRESULT_FROM_WIN32(result) );
|
|
}
|
|
|
|
m_localSocket = WSASocket(
|
|
AF_INET6,
|
|
SOCK_DGRAM,
|
|
IPPROTO_UDP,
|
|
NULL,
|
|
0,
|
|
WSA_FLAG_OVERLAPPED
|
|
);
|
|
|
|
if ( m_localSocket == INVALID_SOCKET )
|
|
{
|
|
result = WSAGetLastError();
|
|
LogMeshPacketManagerComment( L"Error: Failed creating a socket" );
|
|
throw ref new Platform::COMException( HRESULT_FROM_WIN32((unsigned int)result) );
|
|
}
|
|
|
|
// set sockets options for exclusive IPv6.
|
|
int v6only = 0;
|
|
result = setsockopt(
|
|
m_localSocket,
|
|
IPPROTO_IPV6,
|
|
IPV6_V6ONLY,
|
|
(char*) &v6only,
|
|
sizeof( v6only )
|
|
);
|
|
|
|
if ( result != 0 )
|
|
{
|
|
result = WSAGetLastError();
|
|
LogMeshPacketManagerComment( L"Error: setsockopt() failed" );
|
|
throw ref new Platform::COMException( HRESULT_FROM_WIN32(result) );
|
|
}
|
|
|
|
// set sockets to non-blocking
|
|
unsigned long nonBlockingValue = 1;
|
|
result = ioctlsocket(m_localSocket, FIONBIO, &nonBlockingValue);
|
|
if ( result != 0 )
|
|
{
|
|
result = WSAGetLastError();
|
|
LogMeshPacketManagerComment( L"Error: ioctlsocket() failed" );
|
|
throw ref new Platform::COMException( HRESULT_FROM_WIN32(result) );
|
|
}
|
|
|
|
ZeroMemory( &m_localSockAddress, sizeof( m_localSockAddress ) );
|
|
m_localSockAddress.sin6_family = AF_INET6;
|
|
m_localSockAddress.sin6_port = transportLevelPortNumber;
|
|
LogMeshPacketManagerComment( L"Binding to port " + transportLevelPortNumber.ToString() );
|
|
|
|
unsigned long reuse = 1;
|
|
result = setsockopt( m_localSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse) );
|
|
if ( result != 0 )
|
|
{
|
|
result = WSAGetLastError();
|
|
LogMeshPacketManagerComment( L"Error: setsockopt(SO_REUSEADDR) failed" );
|
|
}
|
|
|
|
// bind IPv6 socket.
|
|
result = bind(
|
|
m_localSocket,
|
|
(SOCKADDR*) &m_localSockAddress,
|
|
sizeof( m_localSockAddress )
|
|
);
|
|
if ( result != 0 )
|
|
{
|
|
result = WSAGetLastError();
|
|
LogMeshPacketManagerComment( L"Error: bind() failed" );
|
|
throw ref new Platform::COMException( HRESULT_FROM_WIN32(result) );
|
|
}
|
|
|
|
LogMeshPacketManagerComment( L"Starting thread to listening for network traffic" );
|
|
int32 threadAffinityMask = ~0x04; // Means to this thread can run all everything except core 3 (which is reserved for graphics for example).
|
|
m_socketReceiveThread = ref new MeshThread(0, threadAffinityMask, NORMAL_PRIORITY_CLASS);
|
|
m_socketReceiveThread->OnDoWork += ref new Windows::Foundation::EventHandler<ProcessThreadsEventArgs^>( [this]( Platform::Object^, ProcessThreadsEventArgs^ args )
|
|
{
|
|
SocketReceiveWorkerThreadDoWork(args);
|
|
});
|
|
|
|
LogMeshPacketManagerComment( L"Starting thread to send network traffic" );
|
|
threadAffinityMask = ~0x04; // Means to this thread can run all everything except core 3 (which is reserved for graphics for example).
|
|
m_socketSendThread = ref new MeshThread(INFINITE, threadAffinityMask, NORMAL_PRIORITY_CLASS); // 0xFFFFFFFF == INFINITE. This thread will only wake up when the code tells it to, or upon shutdown
|
|
m_socketSendThread->OnDoWork += ref new Windows::Foundation::EventHandler<ProcessThreadsEventArgs^>( [this]( Platform::Object^, ProcessThreadsEventArgs^ args )
|
|
{
|
|
SocketSendWorkerThreadDoWork(args);
|
|
});
|
|
|
|
m_timerLastSendReliablePacketsUntilACK.QuadPart = 0;
|
|
if (!QueryPerformanceFrequency(&m_timerFrequency))
|
|
{
|
|
THROW_HR( E_UNEXPECTED );
|
|
}
|
|
}
|
|
|
|
uint8 MeshPacketManager::GetLocalConsoleId()
|
|
{
|
|
return m_localConsoleId;
|
|
}
|
|
|
|
void MeshPacketManager::Shutdown()
|
|
{
|
|
if (m_socketSendThread != nullptr)
|
|
{
|
|
m_socketSendThread->Shutdown();
|
|
m_socketSendThread = nullptr;
|
|
}
|
|
|
|
if (m_socketReceiveThread != nullptr)
|
|
{
|
|
m_socketReceiveThread->Shutdown();
|
|
m_socketReceiveThread = nullptr;
|
|
}
|
|
|
|
// close socket after the send and receive threads are shutdown
|
|
// otherwise the threads will attempt to use an invalid socket and throw exceptions
|
|
if (m_localSocket != INVALID_SOCKET )
|
|
{
|
|
shutdown( m_localSocket, SD_BOTH );
|
|
closesocket( m_localSocket );
|
|
m_localSocket = INVALID_SOCKET;
|
|
}
|
|
|
|
WSACleanup();
|
|
}
|
|
|
|
void MeshPacketManager::SendHeartbeatMessageAsync(
|
|
Windows::Xbox::Networking::SecureDeviceAssociation^ association,
|
|
uint8 consoleId
|
|
)
|
|
{
|
|
size_t packetSize = sizeof(MeshPacketHeader) + m_heartbeatMessageSize;
|
|
|
|
std::shared_ptr<MESH_PACKET_INFO> packetInfo( new MESH_PACKET_INFO );
|
|
packetInfo->association = association;
|
|
|
|
GetPacketWithHeader(packetSize, (uint8)MessageTypeEnum::GAME_HEARTBEAT_DATA, packetInfo->packetBuffer, false);
|
|
QueuePacketToSend( packetInfo );
|
|
|
|
MeshHeartbeatStatisticsForConnection^ stats;
|
|
|
|
stats = m_meshPacketStatistics->GetStatForConnection(consoleId);
|
|
stats->SetLastHeartbeatSent(Utils::GetCurrentTime());
|
|
}
|
|
|
|
void MeshPacketManager::SendHelloMessage(
|
|
Windows::Xbox::Networking::SecureDeviceAssociation^ association,
|
|
Platform::String^ consoleName,
|
|
bool respondingToHello
|
|
)
|
|
{
|
|
size_t consoleNameSizeInChars = consoleName->Length(); // WCHAR, one element already there so handy null terminator
|
|
size_t consoleNameSizeInBytes = consoleNameSizeInChars * 2;
|
|
size_t packetSize = sizeof(MeshPacketHeader) + sizeof(MeshPacketHelloMessageHeader) + consoleNameSizeInBytes;
|
|
|
|
std::shared_ptr<MESH_PACKET_INFO> packetInfo( new MESH_PACKET_INFO );
|
|
packetInfo->association = association;
|
|
|
|
GetPacketWithHeader(packetSize, (uint8)MessageTypeEnum::GAME_HELLO_DATA, packetInfo->packetBuffer, false);
|
|
|
|
// Fill out a MeshPacketHelloMessageHeader struct, which appears after the MeshPacketHeader
|
|
BYTE* meshPacketHelloMessageDataPtr = packetInfo->packetBuffer.data() + sizeof(MeshPacketHeader);
|
|
MeshPacketHelloMessageHeader& meshPacketHelloMessageData = (MeshPacketHelloMessageHeader&)*meshPacketHelloMessageDataPtr;
|
|
meshPacketHelloMessageData.respondingToHello = respondingToHello;
|
|
meshPacketHelloMessageData.consoleNameLength = (uint16)(consoleNameSizeInChars);
|
|
|
|
// Fill out a console name string, which appears after the MeshPacketHelloMessageHeader
|
|
BYTE* consoleNamePtr = packetInfo->packetBuffer.data() + sizeof(MeshPacketHeader) + sizeof(MeshPacketHelloMessageHeader);
|
|
memcpy_s(consoleNamePtr, packetSize - sizeof(MeshPacketHelloMessageHeader) - sizeof(MeshPacketHeader), consoleName->Data(), consoleNameSizeInBytes);
|
|
|
|
QueuePacketToSend( packetInfo );
|
|
}
|
|
|
|
void MeshPacketManager::SendChatMessage(
|
|
Windows::Xbox::Networking::SecureDeviceAssociation^ association,
|
|
Windows::Storage::Streams::IBuffer^ buffer,
|
|
bool sendReliable
|
|
)
|
|
{
|
|
size_t packetSize = sizeof(MeshPacketHeader) + buffer->Length;
|
|
|
|
std::shared_ptr<MESH_PACKET_INFO> packetInfo( new MESH_PACKET_INFO );
|
|
packetInfo->association = association;
|
|
|
|
GetPacketWithHeader(packetSize, (uint8)MessageTypeEnum::GAME_CHAT_DATA, packetInfo->packetBuffer, sendReliable);
|
|
|
|
BYTE* byteBufferPointer;
|
|
Utils::GetBufferBytes(buffer, &byteBufferPointer);
|
|
BYTE* bufferPacketPointer = packetInfo->packetBuffer.data() + sizeof(MeshPacketHeader);
|
|
memcpy_s(bufferPacketPointer, packetSize - sizeof(MeshPacketHeader), byteBufferPointer, buffer->Length);
|
|
|
|
QueuePacketToSend( packetInfo );
|
|
}
|
|
|
|
void MeshPacketManager::SendAckMessage(
|
|
Windows::Xbox::Networking::SecureDeviceAssociation^ association,
|
|
uint16 messageIdToAck
|
|
)
|
|
{
|
|
size_t packetSize = sizeof(MeshPacketHeader);
|
|
std::shared_ptr<MESH_PACKET_INFO> packetInfo( new MESH_PACKET_INFO );
|
|
packetInfo->association = association;
|
|
|
|
GetPacketWithHeader(packetSize, (uint8)MessageTypeEnum::GAME_ACK, packetInfo->packetBuffer, false);
|
|
|
|
// The ACK packet treats MeshPacketHeader's messageId as the message that's being ACK'd
|
|
BYTE* packetBufferPtr = packetInfo->packetBuffer.data();
|
|
MeshPacketHeader& packet = (MeshPacketHeader&)*packetBufferPtr;
|
|
packet.messageId = messageIdToAck;
|
|
|
|
QueuePacketToSend( packetInfo );
|
|
}
|
|
|
|
void MeshPacketManager::SendCustomMessage(
|
|
Windows::Xbox::Networking::SecureDeviceAssociation^ association,
|
|
uint8 messageType,
|
|
Windows::Storage::Streams::IBuffer^ buffer,
|
|
bool sendReliable
|
|
)
|
|
{
|
|
size_t packetSize = sizeof(MeshPacketHeader) + buffer->Length;
|
|
|
|
uint8 baseIndexOfGameCustomData = (uint8)MessageTypeEnum::GAME_CUSTOM_DATA; // eg. 64
|
|
uint16 maxIndex = 256 - baseIndexOfGameCustomData;
|
|
if( messageType >= maxIndex ) // eg. 192 = maxIndex
|
|
{
|
|
LogMeshPacketManagerComment( L"Can not send custom message type that is greater or equal to " + maxIndex.ToString() );
|
|
throw ref new Platform::InvalidArgumentException();
|
|
}
|
|
messageType += baseIndexOfGameCustomData;
|
|
|
|
std::shared_ptr<MESH_PACKET_INFO> packetInfo( new MESH_PACKET_INFO );
|
|
packetInfo->association = association;
|
|
|
|
GetPacketWithHeader(packetSize, messageType, packetInfo->packetBuffer, sendReliable);
|
|
|
|
BYTE* byteBufferPointer;
|
|
Utils::GetBufferBytes(buffer, &byteBufferPointer);
|
|
BYTE* bufferPacketPointer = packetInfo->packetBuffer.data() + sizeof(MeshPacketHeader);
|
|
memcpy_s(bufferPacketPointer, packetSize - sizeof(MeshPacketHeader), byteBufferPointer, buffer->Length);
|
|
|
|
QueuePacketToSend( packetInfo );
|
|
}
|
|
|
|
void
|
|
MeshPacketManager::RecordMessageIfSendingReliable(
|
|
std::shared_ptr<MESH_PACKET_INFO> packetInfo
|
|
)
|
|
{
|
|
// Check if the top bit of the messageId is set
|
|
BYTE* messageBufferPtr = packetInfo->packetBuffer.data();
|
|
MeshPacketHeader& meshPacketHeader = reinterpret_cast<MeshPacketHeader&>(messageBufferPtr);
|
|
uint16 sendReliableBit = 1 << 15;
|
|
uint16 sendReliableBitSet = (meshPacketHeader.messageId & sendReliableBit);
|
|
bool wasSendReliableBitSet = (sendReliableBitSet != 0);
|
|
uint16 packetMessageId = meshPacketHeader.messageId & ~sendReliableBit; // remove the sendReliable bit from the message ID
|
|
|
|
if( wasSendReliableBitSet )
|
|
{
|
|
Concurrency::critical_section::scoped_lock lock(m_meshPacketsThatNeedAckLock);
|
|
bool matchFound = false;
|
|
for each (std::shared_ptr<MESH_PACKET_THAT_NEEDS_ACK> meshPacketThatNeedAck in m_meshPacketsThatNeedAck)
|
|
{
|
|
if( meshPacketThatNeedAck->messageId == packetMessageId )
|
|
{
|
|
matchFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !matchFound )
|
|
{
|
|
std::shared_ptr<MESH_PACKET_THAT_NEEDS_ACK> meshPacketThatNeedAck(new MESH_PACKET_THAT_NEEDS_ACK());
|
|
meshPacketThatNeedAck->messageId = packetMessageId;
|
|
meshPacketThatNeedAck->packetInfo = packetInfo;
|
|
m_meshPacketsThatNeedAck.push_back( meshPacketThatNeedAck );
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
MeshPacketManager::QueuePacketToSend(
|
|
std::shared_ptr<MESH_PACKET_INFO> packetInfo
|
|
)
|
|
{
|
|
RecordMessageIfSendingReliable( packetInfo );
|
|
|
|
{
|
|
Concurrency::critical_section::scoped_lock lock(m_sendLock);
|
|
m_packetsToSend.push( packetInfo );
|
|
}
|
|
m_socketSendThread->WakeupThread();
|
|
}
|
|
|
|
MeshPacketStatistics^ MeshPacketManager::GetMeshPacketStatistics()
|
|
{
|
|
return m_meshPacketStatistics;
|
|
}
|
|
|
|
void MeshPacketManager::GetPacketWithHeader(
|
|
size_t packetSize,
|
|
uint8 messageType,
|
|
std::vector<BYTE>& packetBuffer,
|
|
bool sendReliable
|
|
)
|
|
{
|
|
// Create a packet buffer which std::vector will clean up automatically
|
|
packetBuffer.resize(packetSize, 0x33); // 0x33 for debug testing
|
|
BYTE* messageBufferPtr = packetBuffer.data();
|
|
|
|
// Fill out MeshPacketHeader
|
|
MeshPacketHeader& packet = (MeshPacketHeader&)*messageBufferPtr;
|
|
packet.messageType = messageType;
|
|
packet.consoleId = m_localConsoleId;
|
|
|
|
packet.messageId = IncrementPacketMessageId();
|
|
|
|
if( sendReliable )
|
|
{
|
|
// The top bit of the messageId indicates if the remote machine should send back a GAME_ACK message with this messageId
|
|
uint16 sendReliableBit = 1 << 15;
|
|
packet.messageId |= sendReliableBit;
|
|
}
|
|
packet.messageSize = (uint16)(packetSize);
|
|
}
|
|
|
|
void MeshPacketManager::SocketSendWorkerThreadDoWork( Microsoft::Xbox::Samples::NetworkMesh::ProcessThreadsEventArgs^ args )
|
|
{
|
|
std::shared_ptr<MESH_PACKET_INFO> packetInfo;
|
|
|
|
for(;;)
|
|
{
|
|
packetInfo = nullptr;
|
|
{
|
|
Concurrency::critical_section::scoped_lock lock(m_sendLock);
|
|
if( !m_packetsToSend.empty() )
|
|
{
|
|
packetInfo = m_packetsToSend.front();
|
|
m_packetsToSend.pop();
|
|
}
|
|
}
|
|
|
|
if( packetInfo == nullptr )
|
|
{
|
|
// Nothing to do, so ignore
|
|
break;
|
|
}
|
|
|
|
ProcessSendPacket(packetInfo);
|
|
}
|
|
}
|
|
|
|
void MeshPacketManager::ProcessSendPacket( std::shared_ptr<MESH_PACKET_INFO> packetInfo )
|
|
{
|
|
if (packetInfo->association == nullptr)
|
|
{
|
|
LogMeshPacketManagerComment( L"Invalid association to SendPacket" );
|
|
return;
|
|
}
|
|
|
|
if (packetInfo->packetBuffer.size() == 0)
|
|
{
|
|
LogMeshPacketManagerComment( L"No data for SendPacket" );
|
|
return;
|
|
}
|
|
|
|
if (INVALID_SOCKET == m_localSocket)
|
|
{
|
|
LogMeshPacketManagerComment( L"Can't send data if the socket has not been initialized" );
|
|
return;
|
|
}
|
|
|
|
const BYTE* messageBufferPtr = packetInfo->packetBuffer.data();
|
|
MeshPacketHeader& meshPacketHeader = (MeshPacketHeader&)*messageBufferPtr;
|
|
|
|
static bool logFirstTimeOnly = true;
|
|
if( logFirstTimeOnly )
|
|
{
|
|
logFirstTimeOnly = false;
|
|
LogMeshPacketManagerComment( Utils::GetThreadDescription(L"THREAD: WSASendTo") );
|
|
}
|
|
|
|
// Get the remote IPv6 socket addresses from the peerDeviceAssociation
|
|
SOCKADDR_STORAGE remoteSocketAddress = {0};
|
|
Platform::ArrayReference<BYTE> remoteSocketAddressBytes(
|
|
(BYTE*) &remoteSocketAddress,
|
|
sizeof(remoteSocketAddress)
|
|
);
|
|
packetInfo->association->GetRemoteSocketAddressBytes(remoteSocketAddressBytes);
|
|
|
|
// Collect stats on it before sending it out
|
|
m_meshPacketStatistics->InspectPacket(meshPacketHeader, true);
|
|
|
|
WSABUF wsabuf;
|
|
wsabuf.len = meshPacketHeader.messageSize;
|
|
wsabuf.buf = (CHAR*)&meshPacketHeader;
|
|
DWORD numBytesSent = 0;
|
|
|
|
SetDebugInsideWSASend(true); // for debugging purposes only
|
|
|
|
int result = WSASendTo(
|
|
m_localSocket,
|
|
&wsabuf,
|
|
1,
|
|
&numBytesSent,
|
|
0,
|
|
(SOCKADDR*) &remoteSocketAddress,
|
|
sizeof(remoteSocketAddress),
|
|
nullptr,
|
|
nullptr
|
|
);
|
|
|
|
INT lastError = WSAGetLastError();
|
|
|
|
SetDebugInsideWSASend(false); // for debugging purposes only
|
|
SetDebugTimeSincePacketSend( 0.0f ); // for debugging purposes only
|
|
|
|
if(result != 0 || numBytesSent != meshPacketHeader.messageSize)
|
|
{
|
|
// Ignore and log failure
|
|
LogMeshPacketManagerComment(
|
|
Utils::FormatString(L"WSASendTo. ErrorCode: %d. BytesSent: %d. DesiredBytesSent: %d", lastError, numBytesSent,meshPacketHeader.messageSize )
|
|
);
|
|
}
|
|
}
|
|
|
|
void MeshPacketManager::SocketReceiveWorkerThreadDoWork( Microsoft::Xbox::Samples::NetworkMesh::ProcessThreadsEventArgs^ args )
|
|
{
|
|
// This gets called by the MeshThead class over and over again
|
|
DWORD flags = 0;
|
|
DWORD numberBytesReceived = 0;
|
|
SOCKADDR_STORAGE senderSocketAddress;
|
|
int senderSocketAddressSize = sizeof(senderSocketAddress);
|
|
|
|
WSABUF wsabuf;
|
|
wsabuf.len = WSARECV_BUFFER_SIZE;
|
|
wsabuf.buf = (char*) m_bufferForWSARecv;
|
|
|
|
SetDebugInsideWSAReceive(true); // for debugging purposes only
|
|
|
|
static bool logFirstTimeOnly = true;
|
|
if( logFirstTimeOnly )
|
|
{
|
|
logFirstTimeOnly = false;
|
|
LogMeshPacketManagerComment( Utils::GetThreadDescription(L"THREAD: WSARecvFrom") );
|
|
}
|
|
|
|
int result = WSARecvFrom(
|
|
m_localSocket,
|
|
&wsabuf,
|
|
1,
|
|
&numberBytesReceived,
|
|
&flags,
|
|
(SOCKADDR*) &senderSocketAddress,
|
|
&senderSocketAddressSize,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
SetDebugInsideWSAReceive(false); // for debugging purposes only
|
|
SetDebugTimeSincePacketReceive( 0.0f );
|
|
|
|
if (result != 0 || numberBytesReceived == 0 )
|
|
{
|
|
INT lastError = WSAGetLastError();
|
|
if (lastError != ERROR_SUCCESS)
|
|
{
|
|
// Ignore but log receive errors
|
|
LogMeshPacketManagerComment(
|
|
Utils::FormatString(L"WSARecvFrom. ErrorCode: %d. BytesReceived: %d", result, numberBytesReceived )
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
|
|
MeshConnection^ meshConnection = GetMeshConnection( senderSocketAddress );
|
|
if( meshConnection != nullptr )
|
|
{
|
|
if( meshConnection->GetConnectionStatus() == ConnectionStatus::Disconnected ||
|
|
meshConnection->GetConnectionStatus() == ConnectionStatus::Pending )
|
|
{
|
|
LogMeshPacketManagerComment( L"ERROR: Receiving data from console who isn't connected. " + meshConnection->GetConsoleName() );
|
|
LogMeshPacketManagerComment( L"This can happen when the OnAssociationIncoming() event fires after the first hello packet" );
|
|
|
|
return;
|
|
}
|
|
|
|
DWORD offset = 0;
|
|
while( offset < numberBytesReceived )
|
|
{
|
|
BYTE* packetBuffer = m_bufferForWSARecv + offset;
|
|
MeshPacketHeader& meshPacketHeader = reinterpret_cast<MeshPacketHeader&>(*packetBuffer);
|
|
if( offset + meshPacketHeader.messageSize > numberBytesReceived)
|
|
{
|
|
// Invalid packet, so skip it
|
|
LogMeshPacketManagerComment( L"ERROR: Invalid packet sent to us" );
|
|
break;
|
|
}
|
|
|
|
ProcessPacket(meshConnection, packetBuffer);
|
|
offset += meshPacketHeader.messageSize;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogMeshPacketManagerComment( L"ERROR: Receiving data from console who isn't known" );
|
|
LogMeshPacketManagerComment( L"This can happen when the OnAssociationIncoming() event fires after the first hello packet" );
|
|
}
|
|
}
|
|
|
|
MeshConnection^ MeshPacketManager::GetMeshConnection( SOCKADDR_STORAGE senderSocketAddress )
|
|
{
|
|
MeshManager^ meshManager = m_meshManager.Resolve<MeshManager>();
|
|
if(meshManager == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// Check if we already know about this connection. If the console id is not 255, use it for
|
|
// a "fast" lookup. If console id is 255, then intentionally skip the use of console id for
|
|
// lookups and go with the "slow" lookup based on GetAssociationBySocketAddressBytes(). 255
|
|
// was chosen since it outside the expected range of 0...63.
|
|
MeshPacketHeader& meshPacketHeader = reinterpret_cast<MeshPacketHeader&>(*m_bufferForWSARecv);
|
|
if (meshPacketHeader.consoleId != 0xFF)
|
|
{
|
|
MeshConnection^ meshConnection = meshManager->GetConnectionFromConsoleId(meshPacketHeader.consoleId);
|
|
if (meshConnection != nullptr)
|
|
{
|
|
return meshConnection;
|
|
}
|
|
}
|
|
|
|
// Do a lookup of the association based on the socket address
|
|
Platform::ArrayReference<BYTE> localSocketAddressBytes(
|
|
(BYTE*) &m_localSockAddress,
|
|
sizeof(m_localSockAddress));
|
|
|
|
Platform::ArrayReference<BYTE> senderSocketAddressBytes(
|
|
(BYTE*) &senderSocketAddress,
|
|
sizeof(senderSocketAddress));
|
|
|
|
Windows::Xbox::Networking::SecureDeviceAddress^ secureDeviceAddress = nullptr;
|
|
try
|
|
{
|
|
auto receivedSecureDeviceAssociation = Windows::Xbox::Networking::SecureDeviceAssociation::GetAssociationBySocketAddressBytes(
|
|
senderSocketAddressBytes,
|
|
localSocketAddressBytes);
|
|
secureDeviceAddress = receivedSecureDeviceAssociation->RemoteSecureDeviceAddress;
|
|
}
|
|
catch (...)
|
|
{
|
|
LogMeshPacketManagerComment( L"Failed getting GetAssociationBySocketAddressBytes" );
|
|
try
|
|
{
|
|
secureDeviceAddress = Windows::Xbox::Networking::SecureDeviceAddress::FromBytes(senderSocketAddressBytes);
|
|
}
|
|
catch (...)
|
|
{
|
|
LogMeshPacketManagerComment( L"ERROR: Failed getting SecureDeviceAddress::FromBytes. Ignoring WSARecvFrom packet" );
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
return meshManager->GetConnectionFromSecureDeviceAddress(secureDeviceAddress);
|
|
}
|
|
|
|
|
|
UINT16 MeshPacketManager::IncrementPacketMessageId()
|
|
{
|
|
UINT16 result = (UINT16)InterlockedIncrement(&m_packetMessageId);
|
|
return result & 0xffff;
|
|
}
|
|
|
|
bool MeshPacketManager::ShouldPacketBeDropped( Microsoft::Xbox::Samples::NetworkMesh::MeshPacketHeader& packet )
|
|
{
|
|
if( !m_dropOutOfOrderPackets )
|
|
{
|
|
return false;
|
|
}
|
|
uint16 incomingPacketMessageId = packet.messageId;
|
|
uint16 previousPacketMessageId = GetPreviousPacketMessageId();
|
|
|
|
uint16 skippedPercentage = ( SKIPPED_PERCENT * 0xffff) / 100;
|
|
// If the incoming is greater than the previous and incoming is less than Prev + 10%, then we should keep it.
|
|
// For e.g. Max = 100; Prev = 90; Incoming = 95; then anything from 90-100, we keep.
|
|
if (incomingPacketMessageId > previousPacketMessageId && incomingPacketMessageId < (previousPacketMessageId + skippedPercentage))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If the incoming is less than the previous and less than the max wrapped packet (Prev + 10% wrapped around), then we should keep it.
|
|
// For e.g. Max = 100; Prev = 95; Incoming = 4; then maxWrapped = (95+10)%100 = 5; Anything from 0-4, we keep.
|
|
uint16 maxPacketNumberToDropWrapped = (previousPacketMessageId + skippedPercentage) % 0xffff;
|
|
if (maxPacketNumberToDropWrapped > 0xffff)
|
|
{
|
|
// This is the wrap round check
|
|
if(incomingPacketMessageId < previousPacketMessageId && incomingPacketMessageId < maxPacketNumberToDropWrapped)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// everything else is dropped.
|
|
return true;
|
|
}
|
|
|
|
void MeshPacketManager::ProcessPacket( MeshConnection^ sender, BYTE* packetBuffer )
|
|
{
|
|
MeshPacketHeader& meshPacketHeader = reinterpret_cast<MeshPacketHeader&>(*packetBuffer);
|
|
|
|
// The top bit of the messageId indicates if the remote machine should send back a GAME_ACK message with this messageId
|
|
uint16 sendReliableBit = 1 << 15;
|
|
uint16 sendReliableBitSet = (meshPacketHeader.messageId & sendReliableBit);
|
|
bool wasSendReliableBitSet = (sendReliableBitSet != 0);
|
|
meshPacketHeader.messageId &= ~sendReliableBit; // remove the sendReliable bit from the message ID
|
|
|
|
if( wasSendReliableBitSet )
|
|
{
|
|
// If this packet had the bit set, then send back an ACK packet to this sender
|
|
SendAckMessage(sender->GetAssociation(), meshPacketHeader.messageId);
|
|
}
|
|
|
|
// MessageTypeEnum::GAME_ACK is unique because the meshPacketHeader.messageId
|
|
// is the message of the ID packet that's being ACK'd.
|
|
// So ignore it for the drop packet feature
|
|
if( meshPacketHeader.messageType != (uint8)MessageTypeEnum::GAME_ACK )
|
|
{
|
|
if(ShouldPacketBeDropped(meshPacketHeader))
|
|
{
|
|
m_meshPacketStatistics->InspectPacket(meshPacketHeader, false);
|
|
m_meshPacketStatistics->PacketDropped(meshPacketHeader, 1);
|
|
//LogMeshPacketManagerComment("Packets dropped. Curr: " + meshPacketHeader.messageId.ToString() + L" Prev: " + GetPreviousPacketMessageId().ToString());
|
|
return;
|
|
}
|
|
|
|
int packetsSkipped = 0;
|
|
uint16 incomingPacketMessageId = meshPacketHeader.messageId;
|
|
uint16 previousPacketMessageId = GetPreviousPacketMessageId();
|
|
|
|
if(incomingPacketMessageId > previousPacketMessageId)
|
|
{
|
|
packetsSkipped = (incomingPacketMessageId - previousPacketMessageId) - 1;
|
|
}
|
|
else
|
|
{
|
|
// If we haven't dropped him, that means we must have wrapped.
|
|
// For e.g. Max = 100; Prev = 95; incoming = 4;
|
|
packetsSkipped = (incomingPacketMessageId + 0xffff) - previousPacketMessageId - 1;
|
|
}
|
|
|
|
SetPreviousPacketMessageId(incomingPacketMessageId);
|
|
m_meshPacketStatistics->InspectPacket(meshPacketHeader, false);
|
|
if(packetsSkipped > 0)
|
|
{
|
|
//LogMeshPacketManagerComment("Packets skipped " + packetsSkipped.ToString());
|
|
m_meshPacketStatistics->PacketSkipped(meshPacketHeader, packetsSkipped);
|
|
}
|
|
}
|
|
|
|
switch(meshPacketHeader.messageType)
|
|
{
|
|
case MessageTypeEnum::GAME_HEARTBEAT_DATA:
|
|
{
|
|
// Logging done in MeshManager::OnHeartbeatReceived
|
|
auto args = ref new MeshHeartbeatReceivedEvent(meshPacketHeader.consoleId, sender);
|
|
OnHeartbeatReceived(this, args);
|
|
}
|
|
break;
|
|
|
|
case MessageTypeEnum::GAME_HELLO_DATA:
|
|
{
|
|
// Logging done in MeshManager::OnHelloReceived
|
|
BYTE* meshPacketHelloMessageDataPtr = packetBuffer + sizeof(MeshPacketHeader);
|
|
MeshPacketHelloMessageHeader& meshPacketHelloMessageData = (MeshPacketHelloMessageHeader&)*meshPacketHelloMessageDataPtr;
|
|
|
|
BYTE* consoleNamePtr = packetBuffer + sizeof(MeshPacketHeader) + sizeof(MeshPacketHelloMessageHeader);
|
|
Platform::String^ consoleName = ref new Platform::String((WCHAR*)consoleNamePtr, meshPacketHelloMessageData.consoleNameLength);
|
|
|
|
auto args = ref new MeshHelloReceivedEvent(
|
|
meshPacketHeader.consoleId,
|
|
sender,
|
|
consoleName,
|
|
meshPacketHelloMessageData.respondingToHello != 0
|
|
);
|
|
|
|
OnHelloReceived(this, args);
|
|
}
|
|
break;
|
|
|
|
case MessageTypeEnum::GAME_CHAT_DATA:
|
|
{
|
|
BYTE* srcBufferPtr = packetBuffer + sizeof(MeshPacketHeader);
|
|
uint32 srcBufferSizeInBytes = meshPacketHeader.messageSize - sizeof(MeshPacketHeader);
|
|
|
|
Windows::Storage::Streams::Buffer^ destBuffer = ref new Windows::Storage::Streams::Buffer( srcBufferSizeInBytes );
|
|
destBuffer->Length = srcBufferSizeInBytes;
|
|
BYTE* destBufferBytes = nullptr;
|
|
Utils::GetBufferBytes( destBuffer, &destBufferBytes );
|
|
memcpy_s(destBufferBytes, destBuffer->Length, srcBufferPtr, srcBufferSizeInBytes);
|
|
|
|
auto args = ref new MeshChatMessageReceivedEvent(
|
|
meshPacketHeader.consoleId,
|
|
sender,
|
|
destBuffer
|
|
);
|
|
|
|
OnChatMessageReceived(this, args);
|
|
}
|
|
break;
|
|
|
|
case MessageTypeEnum::GAME_ACK:
|
|
{
|
|
DeleteMeshPacketWhenGotAck( meshPacketHeader.messageId );
|
|
|
|
auto args = ref new MeshAckReceivedEvent(
|
|
meshPacketHeader.consoleId,
|
|
sender,
|
|
meshPacketHeader.messageId
|
|
);
|
|
|
|
OnAckReceived(this, args);
|
|
}
|
|
break;
|
|
|
|
case MessageTypeEnum::GAME_CUSTOM_DATA:
|
|
default:
|
|
{
|
|
if( meshPacketHeader.messageType < (uint8)MessageTypeEnum::GAME_CUSTOM_DATA )
|
|
{
|
|
// Ignore invalid packets
|
|
LogMeshPacketManagerComment( L"Invalid packet header: " + meshPacketHeader.messageType.ToString() );
|
|
break;
|
|
}
|
|
|
|
BYTE* srcBufferPtr = packetBuffer + sizeof(MeshPacketHeader);
|
|
uint32 srcBufferSizeInBytes = meshPacketHeader.messageSize - sizeof(MeshPacketHeader);
|
|
|
|
Windows::Storage::Streams::Buffer^ destBuffer = ref new Windows::Storage::Streams::Buffer( srcBufferSizeInBytes );
|
|
destBuffer->Length = srcBufferSizeInBytes;
|
|
BYTE* destBufferBytes = nullptr;
|
|
Utils::GetBufferBytes( destBuffer, &destBufferBytes );
|
|
memcpy_s(destBufferBytes, destBuffer->Length, srcBufferPtr, srcBufferSizeInBytes);
|
|
|
|
auto args = ref new GameCustomMessageReceivedEvent(
|
|
meshPacketHeader.consoleId,
|
|
sender,
|
|
meshPacketHeader.messageType - (uint8)MessageTypeEnum::GAME_CUSTOM_DATA,
|
|
destBuffer
|
|
);
|
|
|
|
OnGameCustomMessageReceived(this, args);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MeshPacketManager::LogMeshPacketManagerComment( Platform::String^ message )
|
|
{
|
|
DebugMessageEventArgs^ args = ref new DebugMessageEventArgs( message, S_OK );
|
|
OnDebugMessage(this, args);
|
|
}
|
|
|
|
void MeshPacketManager::LogMeshPacketManagerCommentWithError( Platform::String^ message, HRESULT hr )
|
|
{
|
|
DebugMessageEventArgs^ args = ref new DebugMessageEventArgs( message + Utils::GetErrorString(hr), hr );
|
|
OnDebugMessage(this, args);
|
|
}
|
|
|
|
void MeshPacketManager::UpdateDebugTimers( float timeDelta )
|
|
{
|
|
SetDebugTimeSincePacketReceive( GetDebugTimeSincePacketReceive() + timeDelta );
|
|
SetDebugTimeSincePacketSend( GetDebugTimeSincePacketSend() + timeDelta );
|
|
}
|
|
|
|
float MeshPacketManager::GetDebugTimeSincePacketReceive()
|
|
{
|
|
Concurrency::critical_section::scoped_lock lock(m_debugStatsLock);
|
|
return m_debugTimeSincePacketReceive;
|
|
}
|
|
|
|
void MeshPacketManager::SetDebugTimeSincePacketReceive( float val )
|
|
{
|
|
Concurrency::critical_section::scoped_lock lock(m_debugStatsLock);
|
|
m_debugTimeSincePacketReceive = val;
|
|
}
|
|
|
|
float MeshPacketManager::GetDebugTimeSincePacketSend()
|
|
{
|
|
Concurrency::critical_section::scoped_lock lock(m_debugStatsLock);
|
|
return m_debugTimeSincePacketSend;
|
|
}
|
|
|
|
void MeshPacketManager::SetDebugTimeSincePacketSend( float val )
|
|
{
|
|
Concurrency::critical_section::scoped_lock lock(m_debugStatsLock);
|
|
m_debugTimeSincePacketSend = val;
|
|
}
|
|
|
|
bool MeshPacketManager::GetDebugInsideWSAReceive()
|
|
{
|
|
Concurrency::critical_section::scoped_lock lock(m_debugStatsLock);
|
|
return m_debugInsideWSAReceive;
|
|
}
|
|
|
|
void MeshPacketManager::SetDebugInsideWSAReceive( bool val )
|
|
{
|
|
Concurrency::critical_section::scoped_lock lock(m_debugStatsLock);
|
|
m_debugInsideWSAReceive = val;
|
|
}
|
|
|
|
bool MeshPacketManager::GetDebugInsideWSASend()
|
|
{
|
|
Concurrency::critical_section::scoped_lock lock(m_debugStatsLock);
|
|
return m_debugInsideWSASend;
|
|
}
|
|
|
|
void MeshPacketManager::SetDebugInsideWSASend( bool val )
|
|
{
|
|
Concurrency::critical_section::scoped_lock lock(m_debugStatsLock);
|
|
m_debugInsideWSASend = val;
|
|
}
|
|
|
|
uint16 MeshPacketManager::GetPreviousPacketMessageId()
|
|
{
|
|
Concurrency::critical_section::scoped_lock lock(m_debugStatsLock);
|
|
return m_previousPacketMessageId;
|
|
}
|
|
|
|
void MeshPacketManager::SetPreviousPacketMessageId( uint16 val )
|
|
{
|
|
Concurrency::critical_section::scoped_lock lock(m_debugStatsLock);
|
|
m_previousPacketMessageId = val;
|
|
}
|
|
|
|
void MeshPacketManager::SetDropOutOfOrderPackets( bool val )
|
|
{
|
|
Concurrency::critical_section::scoped_lock lock(m_debugStatsLock);
|
|
m_dropOutOfOrderPackets = val;
|
|
}
|
|
|
|
void MeshPacketManager::SetHeartbeatSize(UINT size)
|
|
{
|
|
Concurrency::critical_section::scoped_lock lock(m_debugStatsLock);
|
|
m_heartbeatMessageSize = size;
|
|
}
|
|
|
|
UINT MeshPacketManager::GetHeartbeatSize()
|
|
{
|
|
Concurrency::critical_section::scoped_lock lock(m_debugStatsLock);
|
|
return m_heartbeatMessageSize;
|
|
}
|
|
|
|
void MeshPacketManager::SendReliablePacketsUntilACK()
|
|
{
|
|
LARGE_INTEGER timeNow;
|
|
if (!QueryPerformanceCounter(&timeNow))
|
|
{
|
|
assert(false);
|
|
}
|
|
|
|
LONGLONG timeDelta = m_timerLastSendReliablePacketsUntilACK.QuadPart - timeNow.QuadPart;
|
|
LONGLONG numberOfMillisecondsSinceLast = 1000 * timeDelta / m_timerFrequency.QuadPart;
|
|
|
|
if( numberOfMillisecondsSinceLast > 1000 )
|
|
{
|
|
m_timerLastSendReliablePacketsUntilACK = timeNow;
|
|
SendReliablePacketsToConsolesWhoHaveNotAcked();
|
|
}
|
|
}
|
|
|
|
|
|
std::vector< std::shared_ptr<MESH_PACKET_THAT_NEEDS_ACK> > MeshPacketManager::GetMeshPacketsThatNeedAckCopy()
|
|
{
|
|
Concurrency::critical_section::scoped_lock lock(m_meshPacketsThatNeedAckLock);
|
|
std::vector< std::shared_ptr<MESH_PACKET_THAT_NEEDS_ACK> > meshPacketsThatNeedAckCopy( m_meshPacketsThatNeedAck );
|
|
return meshPacketsThatNeedAckCopy;
|
|
}
|
|
|
|
void MeshPacketManager::SendReliablePacketsToConsolesWhoHaveNotAcked()
|
|
{
|
|
std::vector< std::shared_ptr<MESH_PACKET_THAT_NEEDS_ACK> > meshPacketsThatNeedAckCopy = GetMeshPacketsThatNeedAckCopy();
|
|
for each (std::shared_ptr<MESH_PACKET_THAT_NEEDS_ACK> meshPacketThatNeedAck in meshPacketsThatNeedAckCopy)
|
|
{
|
|
QueuePacketToSend( meshPacketThatNeedAck->packetInfo );
|
|
}
|
|
}
|
|
|
|
void MeshPacketManager::DeleteMeshPacketWhenGotAck(uint16 messageId)
|
|
{
|
|
Concurrency::critical_section::scoped_lock lock(m_meshPacketsThatNeedAckLock);
|
|
|
|
bool found = false;
|
|
auto iter = m_meshPacketsThatNeedAck.begin();
|
|
for( ; iter != m_meshPacketsThatNeedAck.end(); iter++ )
|
|
{
|
|
std::shared_ptr<MESH_PACKET_THAT_NEEDS_ACK> iterMeshPacketThatNeedAck = *iter;
|
|
if (iterMeshPacketThatNeedAck->messageId == messageId)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found)
|
|
{
|
|
m_meshPacketsThatNeedAck.erase(iter);
|
|
}
|
|
}
|
|
|
|
void MeshPacketManager::DeleteAllPendingAckMeshPackets()
|
|
{
|
|
Concurrency::critical_section::scoped_lock lock(m_meshPacketsThatNeedAckLock);
|
|
m_meshPacketsThatNeedAck.clear();
|
|
}
|
|
|
|
|
|
}}}}
|