From 3f9a4170f165ed3c57d088a51b1281ece33f030b Mon Sep 17 00:00:00 2001 From: Lynix Date: Wed, 9 Mar 2016 13:54:04 +0100 Subject: [PATCH] Network: Add RUdpConnection class (experimental) Currently missing an external RUdpClient class, ping handling, some messages but should basically works Former-commit-id: 6ebd181a4804094c62aedb8e3ba7876a7b06acdc --- include/Nazara/Network/Enums.hpp | 27 ++ include/Nazara/Network/RUdpConnection.hpp | 158 +++++++ include/Nazara/Network/RUdpConnection.inl | 126 ++++++ include/Nazara/Network/RUdpMessage.hpp | 23 ++ src/Nazara/Network/Network.cpp | 8 + src/Nazara/Network/RUdpConnection.cpp | 482 ++++++++++++++++++++++ 6 files changed, 824 insertions(+) create mode 100644 include/Nazara/Network/RUdpConnection.hpp create mode 100644 include/Nazara/Network/RUdpConnection.inl create mode 100644 include/Nazara/Network/RUdpMessage.hpp create mode 100644 src/Nazara/Network/RUdpConnection.cpp diff --git a/include/Nazara/Network/Enums.hpp b/include/Nazara/Network/Enums.hpp index 3d077519a..81c5f7c3d 100644 --- a/include/Nazara/Network/Enums.hpp +++ b/include/Nazara/Network/Enums.hpp @@ -11,6 +11,12 @@ namespace Nz { enum NetCode : UInt16 { + NetCode_Acknowledge = 0x9A4E, + NetCode_AcknowledgeConnection = 0xC108, + NetCode_Ping = 0x96AC, + NetCode_Pong = 0x974C, + NetCode_RequestConnection = 0xF27D, + NetCode_Invalid = 0x0000 }; @@ -24,6 +30,27 @@ namespace Nz NetProtocol_Max = NetProtocol_Unknown }; + enum PacketPriority + { + PacketPriority_High = 1, //< High-priority packet, will be sent quickly + PacketPriority_Immediate = 0, //< Immediate priority, will be sent immediately + PacketPriority_Medium = 2, //< Medium-priority packet, will be sent as regular + PacketPriority_Low = 3, //< Low-priority packet, may take some time to be sent + + PacketPriority_Lowest = PacketPriority_Low, + PacketPriority_Highest = PacketPriority_Immediate, + PacketPriority_Max = PacketPriority_Low + }; + + enum PacketReliability + { + PacketReliability_Reliable, //< Packet will be resent if lost + PacketReliability_ReliableOrdered, //< Packet will be resent if lost and will only arrive in order + PacketReliability_Unreliable, //< Packet won't be resent if lost + + PacketReliability_Max = PacketReliability_Unreliable + }; + enum ResolveError { ResolveError_NoError, diff --git a/include/Nazara/Network/RUdpConnection.hpp b/include/Nazara/Network/RUdpConnection.hpp new file mode 100644 index 000000000..20285b6fc --- /dev/null +++ b/include/Nazara/Network/RUdpConnection.hpp @@ -0,0 +1,158 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_RUDPSERVER_HPP +#define NAZARA_RUDPSERVER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + class RUdpClient; + + class NAZARA_NETWORK_API RUdpConnection + { + friend class Network; + + public: + using SequenceIndex = UInt16; + + RUdpConnection(); + RUdpConnection(const RUdpConnection&) = delete; + RUdpConnection(RUdpConnection&&) = default; + ~RUdpConnection() = default; + + inline void Close(); + + bool Connect(const IpAddress& remoteAddress); + bool Connect(const String& hostName, NetProtocol protocol = NetProtocol_Any, const String& service = "http", ResolveError* error = nullptr); + inline void Disconnect(); + + inline IpAddress GetBoundAddress() const; + inline UInt16 GetBoundPort() const; + inline SocketError GetLastError() const; + + inline bool Listen(NetProtocol protocol, UInt16 port = 64266, unsigned int queueSize = 10); + bool Listen(const IpAddress& address, unsigned int queueSize = 10); + + bool PollMessage(RUdpMessage* message); + + bool Send(const IpAddress& clientIp, PacketPriority priority, PacketReliability reliability, const NetPacket& packet); + + inline void SetProtocolId(UInt32 protocolId); + inline void SetTimeBeforeAck(UInt32 ms); + + void Update(); + + RUdpConnection& operator=(const RUdpConnection&) = delete; + RUdpConnection& operator=(RUdpConnection&&) = default; + + static constexpr std::size_t MessageHeader = sizeof(UInt16) + 2 * sizeof(SequenceIndex) + sizeof(UInt32); //< Protocol ID (begin) + Sequence ID + Remote Sequence ID + Ack bitfield + static constexpr std::size_t MessageFooter = sizeof(UInt16); //< Protocol ID (end) + + // Signals: + NazaraSignal(OnConnectedToPeer, RUdpConnection* /*connection*/); + NazaraSignal(OnPeerAcknowledged, RUdpConnection* /*connection*/, const IpAddress& /*adress*/); + NazaraSignal(OnPeerConnection, RUdpConnection* /*connection*/, const IpAddress& /*adress*/); + NazaraSignal(OnPeerDisconnected, RUdpConnection* /*connection*/, const IpAddress& /*adress*/); + + private: + struct PeerData; + struct PendingAckPacket; + struct PendingPacket; + + enum PeerState + { + PeerState_Aknowledged, //< A connection request from this peer has been received, we're waiting for another packet to validate + PeerState_Connected, //< Connection is working in both-ways + PeerState_Connecting, //< A connection request has been made + PeerState_WillAck //< Connected, received one or more packets and has no packets to send, waiting before sending an empty ack packet + }; + + void DisconnectPeer(std::size_t peerIndex); + void EnqueuePacket(PeerData& peer, PacketPriority priority, PacketReliability reliability, const NetPacket& packet); + void EnqueuePacketInternal(PeerData& peer, PacketPriority priority, PacketReliability reliability, NetPacket&& data); + bool InitSocket(NetProtocol protocol); + void ProcessAcks(PeerData& peer, SequenceIndex lastAck, UInt32 ackBits); + PeerData& RegisterPeer(const IpAddress& address, PeerState state); + void OnClientRequestingConnection(const IpAddress& address, SequenceIndex sequenceId, UInt64 token); + void OnPacketLost(PeerData& peer, PendingAckPacket&& packet); + void OnPacketReceived(const IpAddress& peerIp, NetPacket&& packet); + void SendPacket(PeerData& peer, PendingPacket&& packet); + + static inline unsigned int ComputeSequenceDifference(SequenceIndex sequence, SequenceIndex sequence2); + static inline bool HasPendingPackets(PeerData& peer); + static bool Initialize(); + static inline bool IsAckMoreRecent(SequenceIndex ack, SequenceIndex ack2); + static inline bool IsReliable(PacketReliability reliability); + static void Uninitialize(); + + struct PendingPacket + { + PacketPriority priority; + PacketReliability reliability; + NetPacket data; + }; + + struct PendingAckPacket + { + PacketPriority priority; + PacketReliability reliability; + NetPacket data; + SequenceIndex sequenceId; + UInt64 timeSent; + }; + + struct PeerData //TODO: Move this to RUdpClient + { + std::array, PacketPriority_Max + 1> pendingPackets; + std::deque pendingAckQueue; + std::set receivedQueue; + std::size_t index; + PeerState state; + IpAddress address; + SequenceIndex localSequence; + SequenceIndex remoteSequence; + UInt32 roundTripTime; + UInt64 lastPacketTime; + UInt64 lastPingTime; + UInt64 stateData1; + }; + + std::unordered_map m_peerByIP; + std::queue m_receivedMessages; + std::size_t m_peerIterator; + std::vector m_peers; + Bitset m_activeClients; + Clock m_clock; + SocketError m_lastError; + UdpSocket m_socket; + UInt32 m_forceAckSendTime; + UInt32 m_pingInterval; + UInt32 m_protocol; + UInt32 m_timeBeforePing; + UInt32 m_timeBeforeTimeOut; + UInt64 m_currentTime; + bool m_shouldAcceptConnections; + + static std::mt19937_64 s_randomGenerator; + }; +} + +#include + +#endif // NAZARA_RUDPSERVER_HPP \ No newline at end of file diff --git a/include/Nazara/Network/RUdpConnection.inl b/include/Nazara/Network/RUdpConnection.inl new file mode 100644 index 000000000..cfb598bb9 --- /dev/null +++ b/include/Nazara/Network/RUdpConnection.inl @@ -0,0 +1,126 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include + +namespace Nz +{ + inline void RUdpConnection::Close() + { + m_socket.Close(); + } + + inline void RUdpConnection::Disconnect() + { + Close(); + } + + inline IpAddress RUdpConnection::GetBoundAddress() const + { + return m_socket.GetBoundAddress(); + } + + inline UInt16 RUdpConnection::GetBoundPort() const + { + return m_socket.GetBoundPort(); + } + + inline SocketError RUdpConnection::GetLastError() const + { + return m_lastError; + } + + inline bool RUdpConnection::Listen(NetProtocol protocol, UInt16 port, unsigned int queueSize) + { + NazaraAssert(protocol != NetProtocol_Any, "Any protocol not supported for Listen"); //< TODO + NazaraAssert(protocol != NetProtocol_Unknown, "Invalid protocol"); + + IpAddress any; + switch (protocol) + { + case NetProtocol_Any: + case NetProtocol_Unknown: + NazaraInternalError("Invalid protocol Any at this point"); + return false; + + case NetProtocol_IPv4: + any = IpAddress::AnyIpV4; + break; + + case NetProtocol_IPv6: + any = IpAddress::AnyIpV6; + break; + } + + any.SetPort(port); + return Listen(any, queueSize); + } + + inline void RUdpConnection::SetProtocolId(UInt32 protocolId) + { + m_protocol = protocolId; + } + + inline void RUdpConnection::SetTimeBeforeAck(UInt32 ms) + { + m_forceAckSendTime = ms * 1000; //< Store in microseconds for easier handling + } + + inline unsigned int RUdpConnection::ComputeSequenceDifference(SequenceIndex sequence, SequenceIndex sequence2) + { + unsigned int difference; + if (sequence2 > sequence) + difference = std::numeric_limits::max() - sequence2 + sequence; + else + difference = sequence - sequence2; + + return 0; + } + + inline bool RUdpConnection::HasPendingPackets(PeerData& peer) + { + for (unsigned int priority = PacketPriority_Highest; priority <= PacketPriority_Lowest; ++priority) + { + std::vector& pendingPackets = peer.pendingPackets[priority]; + if (!pendingPackets.empty()) + return true; + + pendingPackets.clear(); + } + + return false; + } + + inline bool RUdpConnection::IsAckMoreRecent(SequenceIndex ack, SequenceIndex ack2) + { + constexpr SequenceIndex maxDifference = std::numeric_limits::max() / 2; + + if (ack > ack2) + return ack - ack2 <= maxDifference; + else if (ack2 > ack) + return ack2 - ack > maxDifference; + else + return false; ///< Same ack + } + + inline bool RUdpConnection::IsReliable(PacketReliability reliability) + { + switch (reliability) + { + case PacketReliability_Reliable: + case PacketReliability_ReliableOrdered: + return true; + + case PacketReliability_Unreliable: + return false; + } + + NazaraError("PacketReliability not handled (0x" + String::Number(reliability, 16) + ')'); + return false; + } +} + +#include diff --git a/include/Nazara/Network/RUdpMessage.hpp b/include/Nazara/Network/RUdpMessage.hpp new file mode 100644 index 000000000..5d4fdb4a5 --- /dev/null +++ b/include/Nazara/Network/RUdpMessage.hpp @@ -0,0 +1,23 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_RUDMESSAGE_HPP +#define NAZARA_RUDMESSAGE_HPP + +#include +#include +#include + +namespace Nz +{ + struct RUdpMessage + { + IpAddress from; + NetPacket data; + }; +} + +#endif // NAZARA_RUDMESSAGE_HPP \ No newline at end of file diff --git a/src/Nazara/Network/Network.cpp b/src/Nazara/Network/Network.cpp index 72fa76517..41868b79b 100644 --- a/src/Nazara/Network/Network.cpp +++ b/src/Nazara/Network/Network.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #if defined(NAZARA_PLATFORM_WINDOWS) @@ -55,6 +56,12 @@ namespace Nz return false; } + if (!RUdpConnection::Initialize()) + { + NazaraError("Failed to initialize RUdp"); + return false; + } + onExit.Reset(); NazaraNotice("Initialized: Network module"); @@ -80,6 +87,7 @@ namespace Nz s_moduleReferenceCounter = 0; // Uninitialize module here + RUdpConnection::Uninitialize(); NetPacket::Uninitialize(); SocketImpl::Uninitialize(); diff --git a/src/Nazara/Network/RUdpConnection.cpp b/src/Nazara/Network/RUdpConnection.cpp new file mode 100644 index 000000000..12fdc3d22 --- /dev/null +++ b/src/Nazara/Network/RUdpConnection.cpp @@ -0,0 +1,482 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include + +namespace Nz +{ + RUdpConnection::RUdpConnection() : + m_peerIterator(0), + m_forceAckSendTime(10'000), //< 10ms + m_pingInterval(1'000'000), //< 1s + m_protocol(0x4E4E6574), //< "NNet" + m_timeBeforePing(500'000), //< 0.5s + m_timeBeforeTimeOut(10'000'000), //< 10s + m_currentTime(0), + m_shouldAcceptConnections(true) + { + } + + bool RUdpConnection::Connect(const IpAddress& remoteAddress) + { + NazaraAssert(m_socket.GetState() != SocketState_Bound, "Socket must be bound first"); + NazaraAssert(remoteAddress.IsValid(), "Invalid remote address"); + NazaraAssert(remoteAddress.GetPort() != 0, "Remote address has no port"); + + PeerData& client = RegisterPeer(remoteAddress, PeerState_Connecting); + client.stateData1 = s_randomGenerator(); + + NetPacket connectionRequestPacket(NetCode_RequestConnection); + connectionRequestPacket << client.stateData1; + + EnqueuePacket(client, PacketPriority_Immediate, PacketReliability_Unreliable, connectionRequestPacket); + return true; + } + + bool RUdpConnection::Connect(const String& hostName, NetProtocol protocol, const String& service, ResolveError* error) + { + std::vector results = IpAddress::ResolveHostname(protocol, hostName, service, error); + if (results.empty()) + { + m_lastError = SocketError_ResolveError; + return false; + } + + IpAddress hostnameAddress; + for (const HostnameInfo& result : results) + { + if (!result.address) + continue; + + if (result.socketType != SocketType_UDP) + continue; + + hostnameAddress = result.address; + break; //< Take first valid address + } + + return Connect(hostnameAddress); + } + + bool RUdpConnection::Listen(const IpAddress& address, unsigned int queueSize) + { + if (!InitSocket(address.GetProtocol())) + return false; + + return m_socket.Bind(address) == SocketState_Bound; + } + + bool RUdpConnection::PollMessage(RUdpMessage* message) + { + if (m_receivedMessages.empty()) + return false; + + *message = std::move(m_receivedMessages.front()); + m_receivedMessages.pop(); + return true; + } + + bool RUdpConnection::Send(const IpAddress& peerIp, PacketPriority priority, PacketReliability reliability, const NetPacket& packet) + { + auto it = m_peerByIP.find(peerIp); + if (it == m_peerByIP.end()) + return false; /// Silently fail (probably a disconnected client) + + EnqueuePacket(m_peers[it->second], priority, reliability, packet); + return true; + } + + void RUdpConnection::Update() + { + m_currentTime = m_clock.GetMicroseconds(); + + NetPacket receivedPacket; + IpAddress senderIp; + while (m_socket.ReceivePacket(&receivedPacket, &senderIp)) + OnPacketReceived(senderIp, std::move(receivedPacket)); + + //for (unsigned int i = m_activeClients.FindFirst(); i != m_activeClients.npos; i = m_activeClients.FindNext(i)) + //{ + // PeerData& clientData = m_peers[i]; + + CallOnExit resetIterator([this] () { m_peerIterator = m_peers.size(); }); + + for (m_peerIterator = 0; m_peerIterator < m_peers.size(); ++m_peerIterator) + { + PeerData& peer = m_peers[m_peerIterator]; + + UInt32 timeSinceLastPacket = m_currentTime - peer.lastPacketTime; + if (timeSinceLastPacket > m_timeBeforeTimeOut) + { + DisconnectPeer(peer.index); + continue; + } + else if (timeSinceLastPacket > m_timeBeforePing) + { + if (m_currentTime - peer.lastPingTime > m_pingInterval) + { + NetPacket pingPacket(NetCode_Ping); + EnqueuePacket(peer, PacketPriority_Low, PacketReliability_Unreliable, pingPacket); + } + } + + if (peer.state == PeerState_WillAck && m_currentTime - peer.stateData1 > m_forceAckSendTime) + { + NetPacket acknowledgePacket(NetCode_Acknowledge); + EnqueuePacket(peer, PacketPriority_Low, PacketReliability_Reliable, acknowledgePacket); + } + + for (unsigned int priority = PacketPriority_Highest; priority <= PacketPriority_Lowest; ++priority) + { + std::vector& pendingPackets = peer.pendingPackets[priority]; + for (PendingPacket& packetData : pendingPackets) + SendPacket(peer, std::move(packetData)); + + pendingPackets.clear(); + } + + auto it = peer.pendingAckQueue.begin(); + while (it != peer.pendingAckQueue.end()) + { + if (m_currentTime - it->timeSent > 2 * peer.roundTripTime) + { + OnPacketLost(peer, std::move(*it)); + it = peer.pendingAckQueue.erase(it); + } + else + ++it; + } + } + //m_activeClients.Reset(); + } + + void RUdpConnection::DisconnectPeer(std::size_t peerIndex) + { + PeerData& peer = m_peers[peerIndex]; + NazaraNotice(m_socket.GetBoundAddress().ToString() + ": " + peer.address.ToString() + " has been disconnected due to time-out"); + + OnPeerDisconnected(this, peer.address); + + // Remove from IP lookup table + m_peerByIP.erase(peer.address); + + // Can we safely "remove" this slot? + if (m_peerIterator >= m_peers.size() - 1 || peerIndex > m_peerIterator) + { + // Yes we can + PeerData& newSlot = m_peers[peerIndex]; + newSlot = std::move(m_peers.back()); + newSlot.index = peerIndex; //< Update the moved slot index before resizing (in case it's the last one) + } + else + { + // Nope, let's be tricky + PeerData& current = m_peers[m_peerIterator]; + PeerData& newSlot = m_peers[peerIndex]; + + newSlot = std::move(current); + newSlot.index = peerIndex; //< Update the moved slot index + + current = std::move(m_peers.back()); + current.index = m_peerIterator; //< Update the moved slot index + + --m_peerIterator; + } + + // Pop the last entry (from where we moved our slot) + m_peers.pop_back(); + } + + void RUdpConnection::EnqueuePacket(PeerData& peer, PacketPriority priority, PacketReliability reliability, const NetPacket& packet) + { + UInt16 protocolBegin = static_cast(m_protocol & 0xFFFF); + UInt16 protocolEnd = static_cast((m_protocol & 0xFFFF0000) >> 16); + + NetPacket data(packet.GetNetCode(), MessageHeader + packet.GetDataSize() + MessageFooter); + data << protocolBegin; + + data.GetStream()->SetCursorPos(NetPacket::HeaderSize + MessageHeader); + data.Write(packet.GetConstData() + NetPacket::HeaderSize, packet.GetDataSize()); + + data << protocolEnd; + EnqueuePacketInternal(peer, priority, reliability, std::move(data)); + } + + void RUdpConnection::EnqueuePacketInternal(PeerData& peer, PacketPriority priority, PacketReliability reliability, NetPacket&& data) + { + PendingPacket pendingPacket; + pendingPacket.data = std::move(data); + pendingPacket.priority = priority; + pendingPacket.reliability = reliability; + + peer.pendingPackets[priority].emplace_back(std::move(pendingPacket)); + m_activeClients.UnboundedSet(peer.index); + } + + bool RUdpConnection::InitSocket(NetProtocol protocol) + { + CallOnExit updateLastError([this] + { + m_lastError = m_socket.GetLastError(); + }); + + if (!m_socket.Create(protocol)) + return false; + + m_socket.EnableBlocking(false); + return true; + } + + void RUdpConnection::ProcessAcks(PeerData& peer, SequenceIndex lastAck, UInt32 ackBits) + { + auto it = peer.pendingAckQueue.begin(); + while (it != peer.pendingAckQueue.end()) + { + bool acked = false; + if (lastAck == it->sequenceId) + acked = true; + else if (!IsAckMoreRecent(it->sequenceId, lastAck)) + { + unsigned int difference = ComputeSequenceDifference(lastAck, it->sequenceId); + if (difference <= 32) + acked = (ackBits >> (difference - 1)) & 1; + } + + if (acked) + { + it = peer.pendingAckQueue.erase(it); + } + else + ++it; + } + } + + RUdpConnection::PeerData& RUdpConnection::RegisterPeer(const IpAddress& address, PeerState state) + { + PeerData data; + data.address = address; + data.localSequence = 0; + data.remoteSequence = 0; + data.index = m_peers.size(); + data.lastPacketTime = m_currentTime; + data.lastPingTime = m_currentTime; + data.roundTripTime = 1000000; ///< Okay that's quite a lot + data.state = state; + + m_activeClients.UnboundedSet(data.index); + m_peerByIP[address] = data.index; + + m_peers.emplace_back(std::move(data)); + return m_peers.back(); + } + + void RUdpConnection::OnClientRequestingConnection(const IpAddress& address, SequenceIndex sequenceId, UInt64 token) + { + // Call hook to check if client should be accepted or not + OnPeerConnection(this, address); + + PeerData& client = RegisterPeer(address, PeerState_Aknowledged); + client.remoteSequence = sequenceId; + + /// Acknowledge connection + NetPacket connectionAcceptedPacket(NetCode_AcknowledgeConnection); + //connectionAcceptedPacket << address; + connectionAcceptedPacket << ~token; + + EnqueuePacket(client, PacketPriority_Immediate, PacketReliability_Reliable, connectionAcceptedPacket); + } + + void RUdpConnection::OnPacketLost(PeerData& peer, PendingAckPacket&& packet) + { + //NazaraNotice(m_socket.GetBoundAddress().ToString() + ": Lost packet " + String::Number(packet.sequenceId)); + + if (IsReliable(packet.reliability)) + EnqueuePacketInternal(peer, packet.priority, packet.reliability, std::move(packet.data)); + } + + void RUdpConnection::OnPacketReceived(const IpAddress& peerIp, NetPacket&& packet) + { + UInt16 protocolBegin; + UInt16 protocolEnd; + SequenceIndex sequenceId; + SequenceIndex lastAck; + UInt32 ackBits; + + packet.GetStream()->SetCursorPos(packet.GetSize() - MessageFooter); + packet >> protocolEnd; + + packet.GetStream()->SetCursorPos(NetPacket::HeaderSize); + packet >> protocolBegin; + + UInt32 protocolId = static_cast(protocolEnd) << 16 | protocolBegin; + if (protocolId != m_protocol) + return; ///< Ignore + + packet >> sequenceId >> lastAck >> ackBits; + + auto it = m_peerByIP.find(peerIp); + if (it == m_peerByIP.end()) + { + switch (packet.GetNetCode()) + { + case NetCode_RequestConnection: + { + UInt64 token; + packet >> token; + + NazaraNotice(m_socket.GetBoundAddress().ToString() + ": Received NetCode_RequestConnection from " + peerIp.ToString() + ": " + String::Number(token)); + if (!m_shouldAcceptConnections) + return; //< Ignore + + OnClientRequestingConnection(peerIp, sequenceId, token); + break; + } + + default: + return; //< Ignore + } + } + else + { + PeerData& peer = m_peers[it->second]; + peer.lastPacketTime = m_currentTime; + + if (peer.receivedQueue.find(sequenceId) != peer.receivedQueue.end()) + return; //< Ignore + + ///< Receiving a packet from an acknowledged client means the connection works in both ways + if (peer.state == PeerState_Aknowledged && packet.GetNetCode() != NetCode_RequestConnection) + { + peer.state = PeerState_Connected; + OnPeerAcknowledged(this, peerIp); + } + + if (IsAckMoreRecent(sequenceId, peer.remoteSequence)) + peer.remoteSequence = sequenceId; + + ProcessAcks(peer, lastAck, ackBits); + + peer.receivedQueue.insert(sequenceId); + + switch (packet.GetNetCode()) + { + case NetCode_Acknowledge: + return; //< Do not switch to will ack mode (to prevent infinite replies, just let's ping/pong do that) + + case NetCode_AcknowledgeConnection: + { + if (peer.state == PeerState_Connected) + break; + + IpAddress externalAddress; + UInt64 token; + packet /*>> externalAddress*/ >> token; + + NazaraNotice(m_socket.GetBoundAddress().ToString() + ": Received NetCode_AcknowledgeConnection from " + peerIp.ToString() + ": " + String::Number(token)); + if (token == ~peer.stateData1) + { + peer.state = PeerState_Connected; + OnConnectedToPeer(this); + } + else + { + NazaraNotice("Received wrong token (" + String::Number(token) + " instead of " + String::Number(~peer.stateData1) + ") from client " + peer.address); + return; //< Ignore + } + + break; + } + + case NetCode_RequestConnection: + NazaraNotice(m_socket.GetBoundAddress().ToString() + ": Received NetCode_RequestConnection from " + peerIp.ToString()); + return; //< Ignore + + case NetCode_Ping: + { + NazaraNotice(m_socket.GetBoundAddress().ToString() + ": Received NetCode_Ping from " + peerIp.ToString()); + + NetPacket pongPacket(NetCode_Pong); + EnqueuePacket(peer, PacketPriority_Low, PacketReliability_Unreliable, pongPacket); + break; + } + + case NetCode_Pong: + NazaraNotice(m_socket.GetBoundAddress().ToString() + ": Received NetCode_Pong from " + peerIp.ToString()); + break; + + default: + { + NazaraNotice(m_socket.GetBoundAddress().ToString() + ": Received 0x" + String::Number(packet.GetNetCode(), 16) + " from " + peerIp.ToString()); + RUdpMessage receivedMessage; + receivedMessage.from = peerIp; + receivedMessage.data = std::move(packet); + + m_receivedMessages.emplace(std::move(receivedMessage)); + break; + } + } + + if (!HasPendingPackets(peer)) + { + peer.state = PeerState_WillAck; + peer.stateData1 = m_currentTime; + } + } + } + + void RUdpConnection::SendPacket(PeerData& peer, PendingPacket&& packet) + { + if (peer.state == PeerState_WillAck) + peer.state = PeerState_Connected; + + SequenceIndex remoteSequence = peer.remoteSequence; + + UInt32 previousAcks = 0; + for (SequenceIndex ack : peer.receivedQueue) + { + if (ack == remoteSequence) + continue; + + unsigned int difference = ComputeSequenceDifference(remoteSequence, ack); + if (difference <= 32U) + previousAcks |= (1U << (difference - 1)); + } + + SequenceIndex sequenceId = ++peer.localSequence; + + packet.data.GetStream()->SetCursorPos(NetPacket::HeaderSize + sizeof(UInt16)); ///< Protocol begin has already been filled + packet.data << sequenceId; + packet.data << remoteSequence; + packet.data << previousAcks; + + m_socket.SendPacket(peer.address, packet.data); + + PendingAckPacket pendingAckPacket; + pendingAckPacket.data = std::move(packet.data); + pendingAckPacket.priority = packet.priority; + pendingAckPacket.reliability = packet.reliability; + pendingAckPacket.sequenceId = sequenceId; + pendingAckPacket.timeSent = m_currentTime; + + peer.pendingAckQueue.emplace_back(std::move(pendingAckPacket)); + } + + bool RUdpConnection::Initialize() + { + std::random_device device; + s_randomGenerator.seed(device()); + + return true; + } + + inline void RUdpConnection::Uninitialize() + { + } + + std::mt19937_64 RUdpConnection::s_randomGenerator; +}