From 0a30e48d33e2c69be5ccf6e4cf1b756fed27f979 Mon Sep 17 00:00:00 2001 From: "GuillaumeG." Date: Sun, 12 Oct 2025 21:32:32 +0200 Subject: [PATCH 01/13] separate C_server.cpp in 3 cpp files --- CMakeLists.txt | 4 + sources/network/C_netClient.cpp | 689 +++++++++++++++++++ sources/network/C_netServer.cpp | 473 +++++++++++++ sources/network/C_server.cpp | 1104 ------------------------------- 4 files changed, 1166 insertions(+), 1104 deletions(-) create mode 100644 sources/network/C_netClient.cpp create mode 100644 sources/network/C_netServer.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 88dcbb65..a9c9c097 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -285,6 +285,8 @@ target_sources(${FGE_SERVER_LIB_NAME} PRIVATE sources/network/C_packet.cpp sources/network/C_packetLZ4.cpp sources/network/C_server.cpp + sources/network/C_netServer.cpp + sources/network/C_netClient.cpp sources/network/C_socket.cpp sources/network/C_netCommand.cpp sources/network/C_protocol.cpp) @@ -403,6 +405,8 @@ target_sources(${FGE_LIB_NAME} PRIVATE sources/network/C_packet.cpp sources/network/C_packetLZ4.cpp sources/network/C_server.cpp + sources/network/C_netServer.cpp + sources/network/C_netClient.cpp sources/network/C_socket.cpp sources/network/C_netCommand.cpp sources/network/C_protocol.cpp) diff --git a/sources/network/C_netClient.cpp b/sources/network/C_netClient.cpp new file mode 100644 index 00000000..925fd8a9 --- /dev/null +++ b/sources/network/C_netClient.cpp @@ -0,0 +1,689 @@ +/* + * Copyright 2025 Guillaume Guillet + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "FastEngine/manager/network_manager.hpp" +#include "FastEngine/network/C_server.hpp" +#include "private/fge_crypt.hpp" +#include "private/fge_debug.hpp" +#include +#include + +using namespace fge::priv; + +namespace fge::net +{ + +//ClientSideNetUdp +ClientSideNetUdp::ClientSideNetUdp(IpAddress::Types addressType) : + g_socket(addressType) +{ + this->_client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); + this->resetReturnPacket(); +} + +ClientSideNetUdp::~ClientSideNetUdp() +{ + this->stop(); +} + +bool ClientSideNetUdp::start(Port bindPort, + IpAddress const& bindIp, + Port connectRemotePort, + IpAddress const& connectRemoteAddress, + IpAddress::Types addressType) +{ + if (this->g_running) + { + return false; + } + + this->resetReturnPacket(); + + this->g_socket.setAddressType(addressType); + if (addressType == IpAddress::Types::Ipv6) + { + this->g_socket.setIpv6Only(false); + } + else + { + this->g_socket.setDontFragment(true); + } + + this->_client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); + + if (this->g_socket.bind(bindPort, bindIp) == Socket::Errors::ERR_NOERROR) + { + if (this->g_socket.connect(connectRemoteAddress, connectRemotePort) != Socket::Errors::ERR_NOERROR) + { + this->g_socket.close(); + return false; + } + + if (!CryptClientInit(this->g_crypt_ctx)) + { + this->g_socket.close(); + return false; + } + if (!CryptClientCreate(this->g_crypt_ctx, this->_client)) + { + this->g_socket.close(); + return false; + } + + this->g_clientIdentity._ip = connectRemoteAddress; + this->g_clientIdentity._port = connectRemotePort; + + this->g_running = true; + + this->g_threadReception = std::make_unique(&ClientSideNetUdp::threadReception, this); + this->g_threadTransmission = std::make_unique(&ClientSideNetUdp::threadTransmission, this); + + return true; + } + this->g_socket.close(); + return false; +} +void ClientSideNetUdp::stop() +{ + if (this->g_running) + { + this->disconnect().wait(); + + this->g_running = false; + + this->g_threadReception->join(); + this->g_threadTransmission->join(); + + this->g_threadReception = nullptr; + this->g_threadTransmission = nullptr; + + this->g_socket.close(); + + //Clear the flux + this->clearPackets(); + //Clear client + this->_client.clearPackets(); + this->_client.clearLostPacketCount(); + this->_client.setClientPacketCounter(0); + this->_client.setCurrentRealm(FGE_NET_DEFAULT_REALM); + this->_client.setCurrentPacketCounter(0); + + CryptClientDestroy(this->_client); + CryptUninit(this->g_crypt_ctx); + } +} + +void ClientSideNetUdp::notifyTransmission() +{ + this->g_transmissionNotifier.notify_one(); +} + +IpAddress::Types ClientSideNetUdp::getAddressType() const +{ + return this->g_socket.getAddressType(); +} + +bool ClientSideNetUdp::isRunning() const +{ + return this->g_running; +} + +std::future ClientSideNetUdp::retrieveMTU() +{ + if (!this->g_running) + { + throw Exception("Cannot retrieve MTU without a running client"); + } + + auto command = std::make_unique(&this->g_commands); + auto future = command->get_future(); + + std::scoped_lock const lock(this->g_mutexCommands); + this->g_commands.push_back(std::move(command)); + + return future; +} +std::future ClientSideNetUdp::connect(std::string_view versioningString) +{ + if (!this->g_running) + { + throw Exception("Cannot connect without a running client"); + } + + auto command = std::make_unique(&this->g_commands); + auto future = command->get_future(); + command->setVersioningString(versioningString); + + std::scoped_lock const lock(this->g_mutexCommands); + this->g_commands.push_back(std::move(command)); + + return future; +} +std::future ClientSideNetUdp::disconnect() +{ + if (!this->g_running) + { + std::promise promise; + auto future = promise.get_future(); + promise.set_value(); + return future; + } + + this->enableReturnPacket(false); + + auto command = std::make_unique(&this->g_commands); + auto future = command->get_future(); + + std::scoped_lock const lock(this->g_mutexCommands); + this->g_commands.push_back(std::move(command)); + + return future; +} + +Identity const& ClientSideNetUdp::getClientIdentity() const +{ + return this->g_clientIdentity; +} + +FluxProcessResults ClientSideNetUdp::process(ReceivedPacketPtr& packet) +{ + ///TODO: no lock ? + packet.reset(); + + if (this->_client.getStatus().isDisconnected()) + { + this->_g_remainingPackets = 0; + return FluxProcessResults::NONE_AVAILABLE; + } + + //Checking timeout + if (this->_client.getStatus().isTimeout()) + { + this->_client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::TIMEOUT); + this->_g_remainingPackets = 0; + this->clearPackets(); + this->_onClientTimeout.call(*this); + return FluxProcessResults::NONE_AVAILABLE; + } + + //Checking return packet + if (this->isReturnPacketEnabled()) + { + auto const now = std::chrono::steady_clock::now(); + if (now - this->g_returnPacketTimePoint >= this->_client.getPacketReturnRate()) + { + this->g_returnPacketTimePoint = now; + auto returnPacket = this->prepareAndRetrieveReturnPacket(); + this->_onTransmitReturnPacket.call(*this, returnPacket); + this->_client.pushPacket(std::move(returnPacket)); + } + } + + if (this->_g_remainingPackets == 0) + { + this->_g_remainingPackets = this->getPacketsSize(); + return FluxProcessResults::NONE_AVAILABLE; + } + + //Popping the next packet + packet = this->popNextPacket(); + if (!packet) + { + this->_g_remainingPackets = this->getPacketsSize(); + return FluxProcessResults::NONE_AVAILABLE; + } + --this->_g_remainingPackets; + + if (!packet->isMarkedAsLocallyReordered()) + { + this->_client.acknowledgeReception(packet); + } + + auto const stat = PacketReorderer::checkStat(packet, this->_client.getCurrentPacketCounter(), + this->_client.getCurrentRealm()); + + if (!packet->checkFlags(FGE_NET_HEADER_DO_NOT_DISCARD_FLAG)) + { + if (stat == PacketReorderer::Stats::OLD_REALM || stat == PacketReorderer::Stats::OLD_COUNTER) + { +#ifdef FGE_DEF_DEBUG + auto const packetCounter = packet->retrieveCounter().value(); + auto const packetRealm = packet->retrieveRealm().value(); + auto const currentCounter = this->_client.getCurrentPacketCounter(); +#endif + FGE_DEBUG_PRINT("Packet is old, discarding it packetCounter: {}, packetRealm: {}, currentCounter: {}", + packetCounter, packetRealm, currentCounter); + this->_client.advanceLostPacketCount(); + return FluxProcessResults::INTERNALLY_DISCARDED; + } + } + + bool const doNotReorder = packet->checkFlags(FGE_NET_HEADER_DO_NOT_REORDER_FLAG); + if (!doNotReorder && !packet->isMarkedAsLocallyReordered()) + { + auto reorderResult = + this->processReorder(this->_client, packet, this->_client.getCurrentPacketCounter(), false); + if (reorderResult != FluxProcessResults::USER_RETRIEVABLE) + { + return reorderResult; + } + } + + if (packet->retrieveHeaderId().value() == NET_INTERNAL_ID_DISCONNECT) + { + this->_client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); + this->_g_remainingPackets = 0; + this->clearPackets(); + this->_onClientDisconnected.call(*this); + return FluxProcessResults::NONE_AVAILABLE; + } + + if (stat == PacketReorderer::Stats::WAITING_NEXT_REALM || stat == PacketReorderer::Stats::WAITING_NEXT_COUNTER) + { +#ifdef FGE_DEF_DEBUG + auto const packetCounter = packet->retrieveCounter().value(); + auto const packetRealm = packet->retrieveRealm().value(); + auto const currentCounter = this->_client.getCurrentPacketCounter(); +#endif + FGE_DEBUG_PRINT("We lose a packet packetCounter: {}, packetRealm: {}, currentCounter: {}", packetCounter, + packetRealm, currentCounter); + this->_client.advanceLostPacketCount(); //We are missing a packet + } + + if (!doNotReorder) + { + auto const serverCountId = packet->retrieveCounter().value(); + this->_client.setCurrentPacketCounter(serverCountId); + } + auto const serverRealm = packet->retrieveRealm().value(); + this->_client.setCurrentRealm(serverRealm); + + return FluxProcessResults::USER_RETRIEVABLE; +} + +void ClientSideNetUdp::resetReturnPacket() +{ + this->g_returnPacket = CreatePacket(NET_INTERNAL_ID_RETURN_PACKET); + this->g_returnPacket->append(sizeof(this->g_returnPacketEventCount)); + + this->g_returnPacketEventCount = 0; + this->g_isAskingFullUpdate = false; + this->g_returnPacketEventStarted = false; + this->g_returnPacketTimePoint = std::chrono::steady_clock::now(); +} + +TransmitPacketPtr& ClientSideNetUdp::startReturnEvent(ReturnEvents event) +{ + if (this->g_returnPacketEventStarted) + { + throw Exception("Cannot start a new return event without ending the previous one"); + } + this->g_returnPacketEventStarted = true; + ++this->g_returnPacketEventCount; + *this->g_returnPacket << event; + this->g_returnPacketStartPosition = this->g_returnPacket->getDataSize(); + this->g_returnPacket->append(sizeof(uint16_t)); + return this->g_returnPacket; +} + +TransmitPacketPtr& +ClientSideNetUdp::startObjectReturnEvent(uint16_t commandIndex, ObjectSid parentSid, ObjectSid targetSid) +{ + this->startReturnEvent(ReturnEvents::REVT_OBJECT); + *this->g_returnPacket << commandIndex << parentSid << targetSid; + return this->g_returnPacket; +} + +void ClientSideNetUdp::endReturnEvent() +{ + if (!this->g_returnPacketEventStarted) + { + throw Exception("Cannot end a return event without starting one"); + } + this->g_returnPacketEventStarted = false; + + //Rewrite event data size + uint16_t eventSize = this->g_returnPacket->getDataSize() - this->g_returnPacketStartPosition - sizeof(uint16_t); + this->g_returnPacket->pack(this->g_returnPacketStartPosition, &eventSize, sizeof(uint16_t)); +} + +void ClientSideNetUdp::simpleReturnEvent(uint16_t id) +{ + this->startReturnEvent(ReturnEvents::REVT_SIMPLE); + *this->g_returnPacket << id; + this->endReturnEvent(); +} + +void ClientSideNetUdp::askFullUpdateReturnEvent() +{ + if (this->g_isAskingFullUpdate) + { + return; + } + this->g_isAskingFullUpdate = true; + this->startReturnEvent(ReturnEvents::REVT_ASK_FULL_UPDATE); + this->endReturnEvent(); +} + +void ClientSideNetUdp::enableReturnPacket(bool enable) +{ + this->g_returnPacketEnabled = enable; + if (enable) + { + this->resetReturnPacket(); + } +} +bool ClientSideNetUdp::isReturnPacketEnabled() const +{ + return this->g_returnPacketEnabled; +} + +TransmitPacketPtr ClientSideNetUdp::prepareAndRetrieveReturnPacket() +{ + if (this->g_returnPacketEventStarted) + { + throw Exception("Cannot retrieve a return packet without ending the current command"); + } + + auto returnPacket = std::move(this->g_returnPacket); + + //Rewrite events count + returnPacket->packet().pack(ProtocolPacket::HeaderSize, &this->g_returnPacketEventCount, + sizeof(this->g_returnPacketEventCount)); + + //After return events, the packet is mostly composed of timestamp and latency information to limit bandwidth of packets. + //The LatencyPlanner class will do all the work for that. + this->_client._latencyPlanner.pack(returnPacket); + + //Pack acknowledged packets + auto const& acknowledgedPackets = this->_client.getAcknowledgedList(); + SizeType const size = acknowledgedPackets.size(); + returnPacket->packet() << size; + for (auto const& acknowledgedPacket: acknowledgedPackets) + { + returnPacket->packet() << acknowledgedPacket._counter << acknowledgedPacket._realm; + } + this->_client.clearAcknowledgedList(); + + //Prepare the new returnPacket + this->resetReturnPacket(); + + return returnPacket; +} + +std::size_t ClientSideNetUdp::waitForPackets(std::chrono::milliseconds time_ms) +{ + std::unique_lock lock(this->_g_mutexFlux); + auto packetSize = this->_g_packets.size(); + if (packetSize > 0) + { + return packetSize; + } + + this->g_receptionNotifier.wait_for(lock, time_ms); + return this->_g_packets.size(); +} + +void ClientSideNetUdp::threadReception() +{ + Packet pckReceive; + CompressorLZ4 compressor; + + while (this->g_running) + { + if (this->g_socket.select(true, FGE_SERVER_PACKET_RECEPTION_TIMEOUT_MS) == Socket::Errors::ERR_NOERROR) + { + if (this->g_socket.receive(pckReceive) != Socket::Errors::ERR_NOERROR) + { + continue; + } + +#ifdef FGE_ENABLE_CLIENT_NETWORK_RANDOM_LOST + if (fge::_random.range(0, 1000) <= 10) + { + continue; + } +#endif + + //Decrypting the packet if needed + if (this->_client.getStatus().isInEncryptedState()) + { + if (!CryptDecrypt(this->_client, pckReceive)) + { + FGE_DEBUG_PRINT("CryptDecrypt failed"); + continue; + } + } + + auto packet = std::make_unique(std::move(pckReceive), this->g_clientIdentity); + packet->setTimestamp(Client::getTimestamp_ms()); + + //Here we consider that the packet is not encrypted + if (!packet->haveCorrectHeader()) + { + continue; + } + //Skip the header for reading + packet->skip(ProtocolPacket::HeaderSize); + + //Check if the packet is a fragment + if (packet->isFragmented()) + { + auto const result = this->g_defragmentation.process(std::move(packet)); + if (result._result == PacketDefragmentation::Results::RETRIEVABLE) + { + packet = this->g_defragmentation.retrieve(result._id, this->g_clientIdentity); + } + else + { + continue; + } + } + + //Decompress the packet if needed + if (!packet->decompress(compressor)) + { + FGE_DEBUG_PRINT("decompress failed"); + continue; + } + + //Check client status and reset timeout + auto const networkStatus = this->_client.getStatus().getNetworkStatus(); + if (networkStatus != ClientStatus::NetworkStatus::TIMEOUT) + { + //TODO : check if we need to reset the timeout + this->_client.getStatus().resetTimeout(); + } + + auto const headerId = packet->retrieveFullHeaderId().value(); + + //MTU handling + if (networkStatus == ClientStatus::NetworkStatus::ACKNOWLEDGED) + { + switch (headerId & ~FGE_NET_HEADER_FLAGS_MASK) + { + case NET_INTERNAL_ID_MTU_TEST: + { + auto response = CreatePacket(NET_INTERNAL_ID_MTU_TEST_RESPONSE); + response->doNotDiscard().doNotReorder(); + this->_client.pushPacket(std::move(response)); + this->_client.getStatus().resetTimeout(); + FGE_DEBUG_PRINT("received MTU test"); + continue; + } + case NET_INTERNAL_ID_MTU_ASK: + { + auto response = CreatePacket(NET_INTERNAL_ID_MTU_ASK_RESPONSE); + response->doNotDiscard().doNotReorder() << this->g_socket.retrieveCurrentAdapterMTU().value_or(0); + this->_client.pushPacket(std::move(response)); + this->_client.getStatus().resetTimeout(); + FGE_DEBUG_PRINT("received MTU ask"); + continue; + } + case NET_INTERNAL_ID_MTU_FINAL: + FGE_DEBUG_PRINT("received MTU final"); + this->_client._mtuFinalizedFlag = true; + this->_client.getStatus().resetTimeout(); + continue; + } + } + + //Checking commands + { + std::scoped_lock const commandLock(this->g_mutexCommands); + if (!this->g_commands.empty()) + { + + auto const result = + this->g_commands.front()->onReceive(packet, this->g_socket.getAddressType(), this->_client); + if (result == NetCommandResults::SUCCESS || result == NetCommandResults::FAILURE) + { + this->g_commands.pop_front(); + } + + //Commands can drop the packet + if (!packet) + { + continue; + } + } + } + + this->pushPacket(std::move(packet)); + this->g_receptionNotifier.notify_all(); + } + } +} +void ClientSideNetUdp::threadTransmission() +{ + auto lastTimePoint = std::chrono::steady_clock::now(); + std::chrono::milliseconds commandsTime{0}; + CompressorLZ4 compressor; + + std::unique_lock lckServer(this->_g_mutexFlux); + + while (this->g_running) + { + this->g_transmissionNotifier.wait_for(lckServer, std::chrono::milliseconds(10)); + + auto const now = std::chrono::steady_clock::now(); + auto const deltaTime = std::chrono::duration_cast(now - lastTimePoint); + lastTimePoint = now; + + //Checking commands + commandsTime += std::max(std::chrono::milliseconds{1}, deltaTime); + if (commandsTime >= FGE_NET_CMD_UPDATE_TICK_MS) + { + std::scoped_lock const commandLock(this->g_mutexCommands); + if (!this->g_commands.empty()) + { + TransmitPacketPtr possiblePacket; + auto const result = this->g_commands.front()->update(possiblePacket, this->g_socket.getAddressType(), + this->_client, commandsTime); + if (result == NetCommandResults::SUCCESS || result == NetCommandResults::FAILURE) + { + this->g_commands.pop_front(); + } + + if (possiblePacket) + { + //Pushing the packet to the client queue + this->_client.pushPacket(std::move(possiblePacket)); + } + } + + commandsTime = std::chrono::milliseconds::zero(); + } + + //Flux + if (this->_client.isPendingPacketsEmpty()) + { + continue; + } + + if (this->_client.getLastPacketLatency() >= this->_client.getCTOSLatency_ms()) + { //Ready to send ! + auto transmissionPacket = this->_client.popPacket(); + + //Compression and applying options + if (!transmissionPacket->isFragmented() && this->_client.getStatus().isInEncryptedState()) + { + transmissionPacket->applyOptions(this->_client); + if (!transmissionPacket->compress(compressor)) + { + continue; + } + } + else + { + transmissionPacket->applyOptions(this->_client); + } + + //MTU check + if (!transmissionPacket->isFragmented() && + !transmissionPacket->checkFlags(FGE_NET_HEADER_DO_NOT_FRAGMENT_FLAG)) + { + //Packet is not fragmented, we have to check is size + if (this->_client.getMTU() == 0) + { //We don't know the MTU yet + goto mtu_check_skip; + } + + auto fragments = transmissionPacket->fragment(this->_client.getMTU()); + for (std::size_t i = 0; i < fragments.size(); ++i) + { + if (i == 0) + { + transmissionPacket = std::move(fragments[i]); + } + else + { + this->_client.pushForcedFrontPacket(std::move(fragments[i])); + } + } + } + mtu_check_skip: + + if (!transmissionPacket->packet() || !transmissionPacket->haveCorrectHeaderSize()) + { //Last verification of the packet + continue; + } + + //Check if the packet is a disconnection packet + if (transmissionPacket->retrieveHeaderId().value() == NET_INTERNAL_ID_DISCONNECT) + { + this->_client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); + this->_client.getStatus().setTimeout(FGE_NET_STATUS_DEFAULT_TIMEOUT); + this->_client.clearPackets(); + } + + //Check if the packet must be encrypted + if (transmissionPacket->isMarkedForEncryption()) + { + if (!CryptEncrypt(this->_client, *transmissionPacket)) + { + continue; + } + } + + //Sending the packet + this->g_socket.send(transmissionPacket->packet()); + this->_client.resetLastPacketTimePoint(); + } + } +} + +} // namespace fge::net diff --git a/sources/network/C_netServer.cpp b/sources/network/C_netServer.cpp new file mode 100644 index 00000000..99432eaa --- /dev/null +++ b/sources/network/C_netServer.cpp @@ -0,0 +1,473 @@ +/* + * Copyright 2025 Guillaume Guillet + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "FastEngine/manager/network_manager.hpp" +#include "FastEngine/network/C_server.hpp" +#include "private/fge_crypt.hpp" +#include "private/fge_debug.hpp" +#include +#include + +using namespace fge::priv; + +namespace fge::net +{ + +//ServerSideNetUdp +ServerSideNetUdp::ServerSideNetUdp(IpAddress::Types type) : + g_threadReception(nullptr), + g_threadTransmission(nullptr), + g_defaultFlux(*this), + g_socket(type), + g_running(false) +{} +ServerSideNetUdp::~ServerSideNetUdp() +{ + this->stop(); +} + +void ServerSideNetUdp::setVersioningString(std::string_view versioningString) +{ + std::scoped_lock const lock{this->g_mutexServer}; + this->g_versioningString = versioningString; +} +std::string const& ServerSideNetUdp::getVersioningString() const +{ + return this->g_versioningString; +} + +bool ServerSideNetUdp::start(Port bindPort, IpAddress const& bindIp, IpAddress::Types addressType) +{ + if (this->g_running) + { + return false; + } + + this->g_socket.setAddressType(addressType); + if (this->g_socket.bind(bindPort, bindIp) == Socket::Errors::ERR_NOERROR) + { + if (!CryptServerInit(this->g_crypt_ctx)) + { + this->g_socket.close(); + return false; + } + + this->g_running = true; + + this->g_threadReception = std::make_unique(&ServerSideNetUdp::threadReception, this); + this->g_threadTransmission = std::make_unique(&ServerSideNetUdp::threadTransmission, this); + + return true; + } + return false; +} +bool ServerSideNetUdp::start(IpAddress::Types addressType) +{ + if (this->g_running) + { + return false; + } + this->g_socket.setAddressType(addressType); + if (this->g_socket.isValid()) + { + if (!CryptServerInit(this->g_crypt_ctx)) + { + this->g_socket.close(); + return false; + } + + this->g_running = true; + + this->g_threadReception = std::make_unique(&ServerSideNetUdp::threadReception, this); + this->g_threadTransmission = std::make_unique(&ServerSideNetUdp::threadTransmission, this); + + return true; + } + return false; +} +void ServerSideNetUdp::stop() +{ + if (this->g_running) + { + this->g_running = false; + + this->g_threadReception->join(); + this->g_threadTransmission->join(); + + this->g_threadReception = nullptr; + this->g_threadTransmission = nullptr; + + this->g_socket.close(); + + //Clear the flux + std::scoped_lock const lock(this->g_mutexServer); + for (auto& flux: this->g_fluxes) + { + flux->clearPackets(); + } + this->g_defaultFlux.clearPackets(); + decltype(this->g_transmissionQueue)().swap(this->g_transmissionQueue); + + CryptUninit(this->g_crypt_ctx); + } +} + +ServerNetFluxUdp* ServerSideNetUdp::newFlux() +{ + std::scoped_lock const lock(this->g_mutexServer); + + this->g_fluxes.push_back(std::make_unique(*this)); + return this->g_fluxes.back().get(); +} +ServerNetFluxUdp* ServerSideNetUdp::getFlux(std::size_t index) +{ + std::scoped_lock const lock(this->g_mutexServer); + + if (index >= this->g_fluxes.size()) + { + return nullptr; + } + return this->g_fluxes[index].get(); +} +ServerNetFluxUdp* ServerSideNetUdp::getDefaultFlux() +{ + return &this->g_defaultFlux; +} +std::size_t ServerSideNetUdp::getFluxSize() const +{ + return this->g_fluxes.size(); +} +IpAddress::Types ServerSideNetUdp::getAddressType() const +{ + return this->g_socket.getAddressType(); +} +void ServerSideNetUdp::closeFlux(NetFluxUdp* flux) +{ + std::scoped_lock const lock(this->g_mutexServer); + + for (std::size_t i = 0; i < this->g_fluxes.size(); ++i) + { + if (this->g_fluxes[i].get() == flux) + { + this->g_fluxes.erase(this->g_fluxes.begin() + i); + break; + } + } +} +void ServerSideNetUdp::closeAllFlux() +{ + std::scoped_lock const lock(this->g_mutexServer); + this->g_fluxes.clear(); +} + +void ServerSideNetUdp::repushPacket(ReceivedPacketPtr&& packet) +{ + if (!packet->checkFluxLifetime(this->g_fluxes.size())) + { + this->g_defaultFlux.pushPacket(std::move(packet)); + return; + } + this->g_fluxes[packet->getFluxIndex()]->forcePushPacket(std::move(packet)); +} + +void ServerSideNetUdp::notifyTransmission() +{ + this->g_transmissionNotifier.notify_one(); +} + +bool ServerSideNetUdp::isRunning() const +{ + return this->g_running; +} + +void ServerSideNetUdp::notifyNewClient(Identity const& identity, ClientSharedPtr const& client) +{ + std::scoped_lock const lock(this->g_mutexServer); + this->g_clientsMap[identity] = client; +} + +void ServerSideNetUdp::sendTo(TransmitPacketPtr& pck, Client const& client, Identity const& id) +{ + pck->applyOptions(client); + pck->doNotReorder(); + + { + std::scoped_lock const lock(this->g_mutexServer); + this->g_transmissionQueue.emplace(std::move(pck), id); + } + this->g_transmissionNotifier.notify_one(); +} +void ServerSideNetUdp::sendTo(TransmitPacketPtr& pck, Identity const& id) +{ + pck->applyOptions(); + pck->doNotReorder(); + + { + std::scoped_lock const lock(this->g_mutexServer); + this->g_transmissionQueue.emplace(std::move(pck), id); + } + this->g_transmissionNotifier.notify_one(); +} + +void* ServerSideNetUdp::getCryptContext() const +{ + return this->g_crypt_ctx; +} + +void ServerSideNetUdp::threadReception() +{ + Identity idReceive; + Packet pckReceive; + std::size_t pushingIndex = 0; + auto gcClientsMap = std::chrono::steady_clock::now(); + + CompressorLZ4 compressor; + + while (this->g_running) + { + if (this->g_socket.select(true, FGE_SERVER_PACKET_RECEPTION_TIMEOUT_MS) == Socket::Errors::ERR_NOERROR) + { + if (this->g_socket.receiveFrom(pckReceive, idReceive._ip, idReceive._port) == Socket::Errors::ERR_NOERROR) + { +#ifdef FGE_ENABLE_SERVER_NETWORK_RANDOM_LOST + if (fge::_random.range(0, 1000) <= 10) + { + continue; + } +#endif + + auto packet = std::make_unique(std::move(pckReceive), idReceive); + packet->setTimestamp(Client::getTimestamp_ms()); + + std::scoped_lock const lck(this->g_mutexServer); + + auto itClient = this->g_clientsMap.find(idReceive); + if (itClient != this->g_clientsMap.end()) + { + auto client = itClient->second.lock(); + if (!client) + { //bad client + this->g_clientsMap.erase(itClient); + } + else + { + //Check if the packet is encrypted + if (client->getStatus().isInEncryptedState()) + { + if (!CryptDecrypt(*client, *packet)) + { + continue; + } + } + } + } + + //Here we consider that the packet is not encrypted + if (!packet->haveCorrectHeader()) + { + continue; + } + //Skip the header for reading + packet->skip(ProtocolPacket::HeaderSize); + + //Decompress the packet if needed + if (!packet->decompress(compressor)) + { + continue; + } + + //Realm and countId is verified by the flux + + if (this->g_fluxes.empty()) + { + this->g_defaultFlux.pushPacket(std::move(packet)); + continue; + } + + //Try to push packet in a flux + for (std::size_t i = 0; i < this->g_fluxes.size(); ++i) + { + pushingIndex = packet->bumpFluxIndex(this->g_fluxes.size()); + if (this->g_fluxes[pushingIndex]->pushPacket(std::move(packet))) + { //Packet is correctly pushed + break; + } + } + //If every flux is busy, the new packet is dismissed + } + } + + //"Garbage collection" of the clients map + auto const now = std::chrono::steady_clock::now(); + if (std::chrono::duration_cast(now - gcClientsMap) >= + std::chrono::milliseconds{5000}) + { + gcClientsMap = now; + + for (auto it = this->g_clientsMap.begin(); it != this->g_clientsMap.end();) + { + if (it->second.expired()) + { + it = this->g_clientsMap.erase(it); + continue; + } + ++it; + } + } + } +} +void ServerSideNetUdp::threadTransmission() +{ + std::unique_lock lckServer(this->g_mutexServer); + CompressorLZ4 compressor; + std::chrono::steady_clock::time_point timePoint; + + while (this->g_running) + { + this->g_transmissionNotifier.wait_for(lckServer, std::chrono::milliseconds(10)); + + //Checking fluxes + for (std::size_t i = 0; i < this->g_fluxes.size() + 1; ++i) + { + ClientList* clients{nullptr}; + if (i == this->g_fluxes.size()) + { //Doing the default flux + clients = &this->g_defaultFlux._clients; + } + else + { + clients = &this->g_fluxes[i]->_clients; + } + + timePoint = std::chrono::steady_clock::now(); + auto clientLock = clients->acquireLock(); + + for (auto itClient = clients->begin(clientLock); itClient != clients->end(clientLock); ++itClient) + { + auto& client = itClient->second._client; + + //check cache + { + auto const packetCache = client->getPacketCache(); + + auto const clientLatency = + client->getPacketReturnRate() * FGE_NET_PACKET_CACHE_DELAY_FACTOR + + std::chrono::milliseconds(client->_latencyPlanner.getRoundTripTime().value_or(1)); + + while (packetCache.first->check( + timePoint, std::chrono::duration_cast(clientLatency))) + { + FGE_DEBUG_PRINT("re-transmit packet as client didn't acknowledge it"); + client->pushForcedFrontPacket(packetCache.first->pop()); + } + } + + if (client->isPendingPacketsEmpty()) + { + continue; + } + + if (client->getLastPacketLatency() < client->getSTOCLatency_ms()) + { + continue; + } + + auto transmissionPacket = client->popPacket(); + + if (!transmissionPacket->isMarkedAsCached()) + { + //Compression and applying options + if (!transmissionPacket->isFragmented() && client->getStatus().isInEncryptedState()) + { + transmissionPacket->applyOptions(*client); + if (!transmissionPacket->compress(compressor)) + { + continue; + } + auto const packetCache = client->getPacketCache(); + packetCache.first->push(transmissionPacket); + } + else + { + transmissionPacket->applyOptions(*client); + } + } + + //MTU check + if (!transmissionPacket->isFragmented() && + !transmissionPacket->checkFlags(FGE_NET_HEADER_DO_NOT_FRAGMENT_FLAG)) + { + auto const mtu = client->getMTU(); + + //Packet is not fragmented, we have to check is size + if (mtu == 0) + { //We don't know the MTU yet + goto mtu_check_skip; + } + + auto fragments = transmissionPacket->fragment(mtu); + for (std::size_t iFragment = 0; iFragment < fragments.size(); ++iFragment) + { + if (iFragment == 0) + { + transmissionPacket = std::move(fragments[iFragment]); + } + else + { + client->pushForcedFrontPacket(std::move(fragments[iFragment])); + } + } + } + mtu_check_skip: + + if (!transmissionPacket->packet() || !transmissionPacket->haveCorrectHeaderSize()) + { //Last verification of the packet + continue; + } + + //Check if the packet must be encrypted + if (transmissionPacket->isMarkedForEncryption()) + { + if (!CryptEncrypt(*client, *transmissionPacket)) + { + continue; + } + } + + //Sending the packet + this->g_socket.sendTo(transmissionPacket->packet(), itClient->first._ip, itClient->first._port); + client->resetLastPacketTimePoint(); + } + } + + //Checking isolated transmission queue TODO: maybe remove all that + while (!this->g_transmissionQueue.empty()) + { + auto data = std::move(this->g_transmissionQueue.front()); + this->g_transmissionQueue.pop(); + + if (!data.first->packet() || !data.first->haveCorrectHeaderSize()) + { //Last verification of the packet + continue; + } + + //Sending the packet + this->g_socket.sendTo(data.first->packet(), data.second._ip, data.second._port); + } + } +} + +} // namespace fge::net diff --git a/sources/network/C_server.cpp b/sources/network/C_server.cpp index 1cdbbbdf..4fd6c349 100644 --- a/sources/network/C_server.cpp +++ b/sources/network/C_server.cpp @@ -771,1108 +771,4 @@ ServerNetFluxUdp::checkCommands(ClientSharedPtr const& refClient, CommandQueue& return result; } -//ServerUdp -ServerSideNetUdp::ServerSideNetUdp(IpAddress::Types type) : - g_threadReception(nullptr), - g_threadTransmission(nullptr), - g_defaultFlux(*this), - g_socket(type), - g_running(false) -{} -ServerSideNetUdp::~ServerSideNetUdp() -{ - this->stop(); -} - -void ServerSideNetUdp::setVersioningString(std::string_view versioningString) -{ - std::scoped_lock const lock{this->g_mutexServer}; - this->g_versioningString = versioningString; -} -std::string const& ServerSideNetUdp::getVersioningString() const -{ - return this->g_versioningString; -} - -bool ServerSideNetUdp::start(Port bindPort, IpAddress const& bindIp, IpAddress::Types addressType) -{ - if (this->g_running) - { - return false; - } - - this->g_socket.setAddressType(addressType); - if (this->g_socket.bind(bindPort, bindIp) == Socket::Errors::ERR_NOERROR) - { - if (!CryptServerInit(this->g_crypt_ctx)) - { - this->g_socket.close(); - return false; - } - - this->g_running = true; - - this->g_threadReception = std::make_unique(&ServerSideNetUdp::threadReception, this); - this->g_threadTransmission = std::make_unique(&ServerSideNetUdp::threadTransmission, this); - - return true; - } - return false; -} -bool ServerSideNetUdp::start(IpAddress::Types addressType) -{ - if (this->g_running) - { - return false; - } - this->g_socket.setAddressType(addressType); - if (this->g_socket.isValid()) - { - if (!CryptServerInit(this->g_crypt_ctx)) - { - this->g_socket.close(); - return false; - } - - this->g_running = true; - - this->g_threadReception = std::make_unique(&ServerSideNetUdp::threadReception, this); - this->g_threadTransmission = std::make_unique(&ServerSideNetUdp::threadTransmission, this); - - return true; - } - return false; -} -void ServerSideNetUdp::stop() -{ - if (this->g_running) - { - this->g_running = false; - - this->g_threadReception->join(); - this->g_threadTransmission->join(); - - this->g_threadReception = nullptr; - this->g_threadTransmission = nullptr; - - this->g_socket.close(); - - //Clear the flux - std::scoped_lock const lock(this->g_mutexServer); - for (auto& flux: this->g_fluxes) - { - flux->clearPackets(); - } - this->g_defaultFlux.clearPackets(); - decltype(this->g_transmissionQueue)().swap(this->g_transmissionQueue); - - CryptUninit(this->g_crypt_ctx); - } -} - -ServerNetFluxUdp* ServerSideNetUdp::newFlux() -{ - std::scoped_lock const lock(this->g_mutexServer); - - this->g_fluxes.push_back(std::make_unique(*this)); - return this->g_fluxes.back().get(); -} -ServerNetFluxUdp* ServerSideNetUdp::getFlux(std::size_t index) -{ - std::scoped_lock const lock(this->g_mutexServer); - - if (index >= this->g_fluxes.size()) - { - return nullptr; - } - return this->g_fluxes[index].get(); -} -ServerNetFluxUdp* ServerSideNetUdp::getDefaultFlux() -{ - return &this->g_defaultFlux; -} -std::size_t ServerSideNetUdp::getFluxSize() const -{ - return this->g_fluxes.size(); -} -IpAddress::Types ServerSideNetUdp::getAddressType() const -{ - return this->g_socket.getAddressType(); -} -void ServerSideNetUdp::closeFlux(NetFluxUdp* flux) -{ - std::scoped_lock const lock(this->g_mutexServer); - - for (std::size_t i = 0; i < this->g_fluxes.size(); ++i) - { - if (this->g_fluxes[i].get() == flux) - { - this->g_fluxes.erase(this->g_fluxes.begin() + i); - break; - } - } -} -void ServerSideNetUdp::closeAllFlux() -{ - std::scoped_lock const lock(this->g_mutexServer); - this->g_fluxes.clear(); -} - -void ServerSideNetUdp::repushPacket(ReceivedPacketPtr&& packet) -{ - if (!packet->checkFluxLifetime(this->g_fluxes.size())) - { - this->g_defaultFlux.pushPacket(std::move(packet)); - return; - } - this->g_fluxes[packet->getFluxIndex()]->forcePushPacket(std::move(packet)); -} - -void ServerSideNetUdp::notifyTransmission() -{ - this->g_transmissionNotifier.notify_one(); -} - -bool ServerSideNetUdp::isRunning() const -{ - return this->g_running; -} - -void ServerSideNetUdp::notifyNewClient(Identity const& identity, ClientSharedPtr const& client) -{ - std::scoped_lock const lock(this->g_mutexServer); - this->g_clientsMap[identity] = client; -} - -void ServerSideNetUdp::sendTo(TransmitPacketPtr& pck, Client const& client, Identity const& id) -{ - pck->applyOptions(client); - pck->doNotReorder(); - - { - std::scoped_lock const lock(this->g_mutexServer); - this->g_transmissionQueue.emplace(std::move(pck), id); - } - this->g_transmissionNotifier.notify_one(); -} -void ServerSideNetUdp::sendTo(TransmitPacketPtr& pck, Identity const& id) -{ - pck->applyOptions(); - pck->doNotReorder(); - - { - std::scoped_lock const lock(this->g_mutexServer); - this->g_transmissionQueue.emplace(std::move(pck), id); - } - this->g_transmissionNotifier.notify_one(); -} - -void* ServerSideNetUdp::getCryptContext() const -{ - return this->g_crypt_ctx; -} - -void ServerSideNetUdp::threadReception() -{ - Identity idReceive; - Packet pckReceive; - std::size_t pushingIndex = 0; - auto gcClientsMap = std::chrono::steady_clock::now(); - - CompressorLZ4 compressor; - - while (this->g_running) - { - if (this->g_socket.select(true, FGE_SERVER_PACKET_RECEPTION_TIMEOUT_MS) == Socket::Errors::ERR_NOERROR) - { - if (this->g_socket.receiveFrom(pckReceive, idReceive._ip, idReceive._port) == Socket::Errors::ERR_NOERROR) - { -#ifdef FGE_ENABLE_SERVER_NETWORK_RANDOM_LOST - if (fge::_random.range(0, 1000) <= 10) - { - continue; - } -#endif - - auto packet = std::make_unique(std::move(pckReceive), idReceive); - packet->setTimestamp(Client::getTimestamp_ms()); - - std::scoped_lock const lck(this->g_mutexServer); - - auto itClient = this->g_clientsMap.find(idReceive); - if (itClient != this->g_clientsMap.end()) - { - auto client = itClient->second.lock(); - if (!client) - { //bad client - this->g_clientsMap.erase(itClient); - } - else - { - //Check if the packet is encrypted - if (client->getStatus().isInEncryptedState()) - { - if (!CryptDecrypt(*client, *packet)) - { - continue; - } - } - } - } - - //Here we consider that the packet is not encrypted - if (!packet->haveCorrectHeader()) - { - continue; - } - //Skip the header for reading - packet->skip(ProtocolPacket::HeaderSize); - - //Decompress the packet if needed - if (!packet->decompress(compressor)) - { - continue; - } - - //Realm and countId is verified by the flux - - if (this->g_fluxes.empty()) - { - this->g_defaultFlux.pushPacket(std::move(packet)); - continue; - } - - //Try to push packet in a flux - for (std::size_t i = 0; i < this->g_fluxes.size(); ++i) - { - pushingIndex = packet->bumpFluxIndex(this->g_fluxes.size()); - if (this->g_fluxes[pushingIndex]->pushPacket(std::move(packet))) - { //Packet is correctly pushed - break; - } - } - //If every flux is busy, the new packet is dismissed - } - } - - //"Garbage collection" of the clients map - auto const now = std::chrono::steady_clock::now(); - if (std::chrono::duration_cast(now - gcClientsMap) >= - std::chrono::milliseconds{5000}) - { - gcClientsMap = now; - - for (auto it = this->g_clientsMap.begin(); it != this->g_clientsMap.end();) - { - if (it->second.expired()) - { - it = this->g_clientsMap.erase(it); - continue; - } - ++it; - } - } - } -} -void ServerSideNetUdp::threadTransmission() -{ - std::unique_lock lckServer(this->g_mutexServer); - CompressorLZ4 compressor; - std::chrono::steady_clock::time_point timePoint; - - while (this->g_running) - { - this->g_transmissionNotifier.wait_for(lckServer, std::chrono::milliseconds(10)); - - //Checking fluxes - for (std::size_t i = 0; i < this->g_fluxes.size() + 1; ++i) - { - ClientList* clients{nullptr}; - if (i == this->g_fluxes.size()) - { //Doing the default flux - clients = &this->g_defaultFlux._clients; - } - else - { - clients = &this->g_fluxes[i]->_clients; - } - - timePoint = std::chrono::steady_clock::now(); - auto clientLock = clients->acquireLock(); - - for (auto itClient = clients->begin(clientLock); itClient != clients->end(clientLock); ++itClient) - { - auto& client = itClient->second._client; - - //check cache - { - auto const packetCache = client->getPacketCache(); - - auto const clientLatency = - client->getPacketReturnRate() * FGE_NET_PACKET_CACHE_DELAY_FACTOR + - std::chrono::milliseconds(client->_latencyPlanner.getRoundTripTime().value_or(1)); - - while (packetCache.first->check( - timePoint, std::chrono::duration_cast(clientLatency))) - { - FGE_DEBUG_PRINT("re-transmit packet as client didn't acknowledge it"); - client->pushForcedFrontPacket(packetCache.first->pop()); - } - } - - if (client->isPendingPacketsEmpty()) - { - continue; - } - - if (client->getLastPacketLatency() < client->getSTOCLatency_ms()) - { - continue; - } - - auto transmissionPacket = client->popPacket(); - - if (!transmissionPacket->isMarkedAsCached()) - { - //Compression and applying options - if (!transmissionPacket->isFragmented() && client->getStatus().isInEncryptedState()) - { - transmissionPacket->applyOptions(*client); - if (!transmissionPacket->compress(compressor)) - { - continue; - } - auto const packetCache = client->getPacketCache(); - packetCache.first->push(transmissionPacket); - } - else - { - transmissionPacket->applyOptions(*client); - } - } - - //MTU check - if (!transmissionPacket->isFragmented() && - !transmissionPacket->checkFlags(FGE_NET_HEADER_DO_NOT_FRAGMENT_FLAG)) - { - auto const mtu = client->getMTU(); - - //Packet is not fragmented, we have to check is size - if (mtu == 0) - { //We don't know the MTU yet - goto mtu_check_skip; - } - - auto fragments = transmissionPacket->fragment(mtu); - for (std::size_t iFragment = 0; iFragment < fragments.size(); ++iFragment) - { - if (iFragment == 0) - { - transmissionPacket = std::move(fragments[iFragment]); - } - else - { - client->pushForcedFrontPacket(std::move(fragments[iFragment])); - } - } - } - mtu_check_skip: - - if (!transmissionPacket->packet() || !transmissionPacket->haveCorrectHeaderSize()) - { //Last verification of the packet - continue; - } - - //Check if the packet must be encrypted - if (transmissionPacket->isMarkedForEncryption()) - { - if (!CryptEncrypt(*client, *transmissionPacket)) - { - continue; - } - } - - //Sending the packet - this->g_socket.sendTo(transmissionPacket->packet(), itClient->first._ip, itClient->first._port); - client->resetLastPacketTimePoint(); - } - } - - //Checking isolated transmission queue TODO: maybe remove all that - while (!this->g_transmissionQueue.empty()) - { - auto data = std::move(this->g_transmissionQueue.front()); - this->g_transmissionQueue.pop(); - - if (!data.first->packet() || !data.first->haveCorrectHeaderSize()) - { //Last verification of the packet - continue; - } - - //Sending the packet - this->g_socket.sendTo(data.first->packet(), data.second._ip, data.second._port); - } - } -} - -//ServerClientSideUdp -ClientSideNetUdp::ClientSideNetUdp(IpAddress::Types addressType) : - g_socket(addressType) -{ - this->_client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); - this->resetReturnPacket(); -} - -ClientSideNetUdp::~ClientSideNetUdp() -{ - this->stop(); -} - -bool ClientSideNetUdp::start(Port bindPort, - IpAddress const& bindIp, - Port connectRemotePort, - IpAddress const& connectRemoteAddress, - IpAddress::Types addressType) -{ - if (this->g_running) - { - return false; - } - - this->resetReturnPacket(); - - this->g_socket.setAddressType(addressType); - if (addressType == IpAddress::Types::Ipv6) - { - this->g_socket.setIpv6Only(false); - } - else - { - this->g_socket.setDontFragment(true); - } - - this->_client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); - - if (this->g_socket.bind(bindPort, bindIp) == Socket::Errors::ERR_NOERROR) - { - if (this->g_socket.connect(connectRemoteAddress, connectRemotePort) != Socket::Errors::ERR_NOERROR) - { - this->g_socket.close(); - return false; - } - - if (!CryptClientInit(this->g_crypt_ctx)) - { - this->g_socket.close(); - return false; - } - if (!CryptClientCreate(this->g_crypt_ctx, this->_client)) - { - this->g_socket.close(); - return false; - } - - this->g_clientIdentity._ip = connectRemoteAddress; - this->g_clientIdentity._port = connectRemotePort; - - this->g_running = true; - - this->g_threadReception = std::make_unique(&ClientSideNetUdp::threadReception, this); - this->g_threadTransmission = std::make_unique(&ClientSideNetUdp::threadTransmission, this); - - return true; - } - this->g_socket.close(); - return false; -} -void ClientSideNetUdp::stop() -{ - if (this->g_running) - { - this->disconnect().wait(); - - this->g_running = false; - - this->g_threadReception->join(); - this->g_threadTransmission->join(); - - this->g_threadReception = nullptr; - this->g_threadTransmission = nullptr; - - this->g_socket.close(); - - //Clear the flux - this->clearPackets(); - //Clear client - this->_client.clearPackets(); - this->_client.clearLostPacketCount(); - this->_client.setClientPacketCounter(0); - this->_client.setCurrentRealm(FGE_NET_DEFAULT_REALM); - this->_client.setCurrentPacketCounter(0); - - CryptClientDestroy(this->_client); - CryptUninit(this->g_crypt_ctx); - } -} - -void ClientSideNetUdp::notifyTransmission() -{ - this->g_transmissionNotifier.notify_one(); -} - -IpAddress::Types ClientSideNetUdp::getAddressType() const -{ - return this->g_socket.getAddressType(); -} - -bool ClientSideNetUdp::isRunning() const -{ - return this->g_running; -} - -std::future ClientSideNetUdp::retrieveMTU() -{ - if (!this->g_running) - { - throw Exception("Cannot retrieve MTU without a running client"); - } - - auto command = std::make_unique(&this->g_commands); - auto future = command->get_future(); - - std::scoped_lock const lock(this->g_mutexCommands); - this->g_commands.push_back(std::move(command)); - - return future; -} -std::future ClientSideNetUdp::connect(std::string_view versioningString) -{ - if (!this->g_running) - { - throw Exception("Cannot connect without a running client"); - } - - auto command = std::make_unique(&this->g_commands); - auto future = command->get_future(); - command->setVersioningString(versioningString); - - std::scoped_lock const lock(this->g_mutexCommands); - this->g_commands.push_back(std::move(command)); - - return future; -} -std::future ClientSideNetUdp::disconnect() -{ - if (!this->g_running) - { - std::promise promise; - auto future = promise.get_future(); - promise.set_value(); - return future; - } - - this->enableReturnPacket(false); - - auto command = std::make_unique(&this->g_commands); - auto future = command->get_future(); - - std::scoped_lock const lock(this->g_mutexCommands); - this->g_commands.push_back(std::move(command)); - - return future; -} - -Identity const& ClientSideNetUdp::getClientIdentity() const -{ - return this->g_clientIdentity; -} - -FluxProcessResults ClientSideNetUdp::process(ReceivedPacketPtr& packet) -{ - ///TODO: no lock ? - packet.reset(); - - if (this->_client.getStatus().isDisconnected()) - { - this->_g_remainingPackets = 0; - return FluxProcessResults::NONE_AVAILABLE; - } - - //Checking timeout - if (this->_client.getStatus().isTimeout()) - { - this->_client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::TIMEOUT); - this->_g_remainingPackets = 0; - this->clearPackets(); - this->_onClientTimeout.call(*this); - return FluxProcessResults::NONE_AVAILABLE; - } - - //Checking return packet - if (this->isReturnPacketEnabled()) - { - auto const now = std::chrono::steady_clock::now(); - if (now - this->g_returnPacketTimePoint >= this->_client.getPacketReturnRate()) - { - this->g_returnPacketTimePoint = now; - auto returnPacket = this->prepareAndRetrieveReturnPacket(); - this->_onTransmitReturnPacket.call(*this, returnPacket); - this->_client.pushPacket(std::move(returnPacket)); - } - } - - if (this->_g_remainingPackets == 0) - { - this->_g_remainingPackets = this->getPacketsSize(); - return FluxProcessResults::NONE_AVAILABLE; - } - - //Popping the next packet - packet = this->popNextPacket(); - if (!packet) - { - this->_g_remainingPackets = this->getPacketsSize(); - return FluxProcessResults::NONE_AVAILABLE; - } - --this->_g_remainingPackets; - - if (!packet->isMarkedAsLocallyReordered()) - { - this->_client.acknowledgeReception(packet); - } - - auto const stat = PacketReorderer::checkStat(packet, this->_client.getCurrentPacketCounter(), - this->_client.getCurrentRealm()); - - if (!packet->checkFlags(FGE_NET_HEADER_DO_NOT_DISCARD_FLAG)) - { - if (stat == PacketReorderer::Stats::OLD_REALM || stat == PacketReorderer::Stats::OLD_COUNTER) - { -#ifdef FGE_DEF_DEBUG - auto const packetCounter = packet->retrieveCounter().value(); - auto const packetRealm = packet->retrieveRealm().value(); - auto const currentCounter = this->_client.getCurrentPacketCounter(); -#endif - FGE_DEBUG_PRINT("Packet is old, discarding it packetCounter: {}, packetRealm: {}, currentCounter: {}", - packetCounter, packetRealm, currentCounter); - this->_client.advanceLostPacketCount(); - return FluxProcessResults::INTERNALLY_DISCARDED; - } - } - - bool const doNotReorder = packet->checkFlags(FGE_NET_HEADER_DO_NOT_REORDER_FLAG); - if (!doNotReorder && !packet->isMarkedAsLocallyReordered()) - { - auto reorderResult = - this->processReorder(this->_client, packet, this->_client.getCurrentPacketCounter(), false); - if (reorderResult != FluxProcessResults::USER_RETRIEVABLE) - { - return reorderResult; - } - } - - if (packet->retrieveHeaderId().value() == NET_INTERNAL_ID_DISCONNECT) - { - this->_client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); - this->_g_remainingPackets = 0; - this->clearPackets(); - this->_onClientDisconnected.call(*this); - return FluxProcessResults::NONE_AVAILABLE; - } - - if (stat == PacketReorderer::Stats::WAITING_NEXT_REALM || stat == PacketReorderer::Stats::WAITING_NEXT_COUNTER) - { -#ifdef FGE_DEF_DEBUG - auto const packetCounter = packet->retrieveCounter().value(); - auto const packetRealm = packet->retrieveRealm().value(); - auto const currentCounter = this->_client.getCurrentPacketCounter(); -#endif - FGE_DEBUG_PRINT("We lose a packet packetCounter: {}, packetRealm: {}, currentCounter: {}", packetCounter, - packetRealm, currentCounter); - this->_client.advanceLostPacketCount(); //We are missing a packet - } - - if (!doNotReorder) - { - auto const serverCountId = packet->retrieveCounter().value(); - this->_client.setCurrentPacketCounter(serverCountId); - } - auto const serverRealm = packet->retrieveRealm().value(); - this->_client.setCurrentRealm(serverRealm); - - return FluxProcessResults::USER_RETRIEVABLE; -} - -void ClientSideNetUdp::resetReturnPacket() -{ - this->g_returnPacket = CreatePacket(NET_INTERNAL_ID_RETURN_PACKET); - this->g_returnPacket->append(sizeof(this->g_returnPacketEventCount)); - - this->g_returnPacketEventCount = 0; - this->g_isAskingFullUpdate = false; - this->g_returnPacketEventStarted = false; - this->g_returnPacketTimePoint = std::chrono::steady_clock::now(); -} - -TransmitPacketPtr& ClientSideNetUdp::startReturnEvent(ReturnEvents event) -{ - if (this->g_returnPacketEventStarted) - { - throw Exception("Cannot start a new return event without ending the previous one"); - } - this->g_returnPacketEventStarted = true; - ++this->g_returnPacketEventCount; - *this->g_returnPacket << event; - this->g_returnPacketStartPosition = this->g_returnPacket->getDataSize(); - this->g_returnPacket->append(sizeof(uint16_t)); - return this->g_returnPacket; -} - -TransmitPacketPtr& -ClientSideNetUdp::startObjectReturnEvent(uint16_t commandIndex, ObjectSid parentSid, ObjectSid targetSid) -{ - this->startReturnEvent(ReturnEvents::REVT_OBJECT); - *this->g_returnPacket << commandIndex << parentSid << targetSid; - return this->g_returnPacket; -} - -void ClientSideNetUdp::endReturnEvent() -{ - if (!this->g_returnPacketEventStarted) - { - throw Exception("Cannot end a return event without starting one"); - } - this->g_returnPacketEventStarted = false; - - //Rewrite event data size - uint16_t eventSize = this->g_returnPacket->getDataSize() - this->g_returnPacketStartPosition - sizeof(uint16_t); - this->g_returnPacket->pack(this->g_returnPacketStartPosition, &eventSize, sizeof(uint16_t)); -} - -void ClientSideNetUdp::simpleReturnEvent(uint16_t id) -{ - this->startReturnEvent(ReturnEvents::REVT_SIMPLE); - *this->g_returnPacket << id; - this->endReturnEvent(); -} - -void ClientSideNetUdp::askFullUpdateReturnEvent() -{ - if (this->g_isAskingFullUpdate) - { - return; - } - this->g_isAskingFullUpdate = true; - this->startReturnEvent(ReturnEvents::REVT_ASK_FULL_UPDATE); - this->endReturnEvent(); -} - -void ClientSideNetUdp::enableReturnPacket(bool enable) -{ - this->g_returnPacketEnabled = enable; - if (enable) - { - this->resetReturnPacket(); - } -} -bool ClientSideNetUdp::isReturnPacketEnabled() const -{ - return this->g_returnPacketEnabled; -} - -TransmitPacketPtr ClientSideNetUdp::prepareAndRetrieveReturnPacket() -{ - if (this->g_returnPacketEventStarted) - { - throw Exception("Cannot retrieve a return packet without ending the current command"); - } - - auto returnPacket = std::move(this->g_returnPacket); - - //Rewrite events count - returnPacket->packet().pack(ProtocolPacket::HeaderSize, &this->g_returnPacketEventCount, - sizeof(this->g_returnPacketEventCount)); - - //After return events, the packet is mostly composed of timestamp and latency information to limit bandwidth of packets. - //The LatencyPlanner class will do all the work for that. - this->_client._latencyPlanner.pack(returnPacket); - - //Pack acknowledged packets - auto const& acknowledgedPackets = this->_client.getAcknowledgedList(); - SizeType const size = acknowledgedPackets.size(); - returnPacket->packet() << size; - for (auto const& acknowledgedPacket: acknowledgedPackets) - { - returnPacket->packet() << acknowledgedPacket._counter << acknowledgedPacket._realm; - } - this->_client.clearAcknowledgedList(); - - //Prepare the new returnPacket - this->resetReturnPacket(); - - return returnPacket; -} - -std::size_t ClientSideNetUdp::waitForPackets(std::chrono::milliseconds time_ms) -{ - std::unique_lock lock(this->_g_mutexFlux); - auto packetSize = this->_g_packets.size(); - if (packetSize > 0) - { - return packetSize; - } - - this->g_receptionNotifier.wait_for(lock, time_ms); - return this->_g_packets.size(); -} - -void ClientSideNetUdp::threadReception() -{ - Packet pckReceive; - CompressorLZ4 compressor; - - while (this->g_running) - { - if (this->g_socket.select(true, FGE_SERVER_PACKET_RECEPTION_TIMEOUT_MS) == Socket::Errors::ERR_NOERROR) - { - if (this->g_socket.receive(pckReceive) != Socket::Errors::ERR_NOERROR) - { - continue; - } - -#ifdef FGE_ENABLE_CLIENT_NETWORK_RANDOM_LOST - if (fge::_random.range(0, 1000) <= 10) - { - continue; - } -#endif - - //Decrypting the packet if needed - if (this->_client.getStatus().isInEncryptedState()) - { - if (!CryptDecrypt(this->_client, pckReceive)) - { - FGE_DEBUG_PRINT("CryptDecrypt failed"); - continue; - } - } - - auto packet = std::make_unique(std::move(pckReceive), this->g_clientIdentity); - packet->setTimestamp(Client::getTimestamp_ms()); - - //Here we consider that the packet is not encrypted - if (!packet->haveCorrectHeader()) - { - continue; - } - //Skip the header for reading - packet->skip(ProtocolPacket::HeaderSize); - - //Check if the packet is a fragment - if (packet->isFragmented()) - { - auto const result = this->g_defragmentation.process(std::move(packet)); - if (result._result == PacketDefragmentation::Results::RETRIEVABLE) - { - packet = this->g_defragmentation.retrieve(result._id, this->g_clientIdentity); - } - else - { - continue; - } - } - - //Decompress the packet if needed - if (!packet->decompress(compressor)) - { - FGE_DEBUG_PRINT("decompress failed"); - continue; - } - - //Check client status and reset timeout - auto const networkStatus = this->_client.getStatus().getNetworkStatus(); - if (networkStatus != ClientStatus::NetworkStatus::TIMEOUT) - { - //TODO : check if we need to reset the timeout - this->_client.getStatus().resetTimeout(); - } - - auto const headerId = packet->retrieveFullHeaderId().value(); - - //MTU handling - if (networkStatus == ClientStatus::NetworkStatus::ACKNOWLEDGED) - { - switch (headerId & ~FGE_NET_HEADER_FLAGS_MASK) - { - case NET_INTERNAL_ID_MTU_TEST: - { - auto response = CreatePacket(NET_INTERNAL_ID_MTU_TEST_RESPONSE); - response->doNotDiscard().doNotReorder(); - this->_client.pushPacket(std::move(response)); - this->_client.getStatus().resetTimeout(); - FGE_DEBUG_PRINT("received MTU test"); - continue; - } - case NET_INTERNAL_ID_MTU_ASK: - { - auto response = CreatePacket(NET_INTERNAL_ID_MTU_ASK_RESPONSE); - response->doNotDiscard().doNotReorder() << this->g_socket.retrieveCurrentAdapterMTU().value_or(0); - this->_client.pushPacket(std::move(response)); - this->_client.getStatus().resetTimeout(); - FGE_DEBUG_PRINT("received MTU ask"); - continue; - } - case NET_INTERNAL_ID_MTU_FINAL: - FGE_DEBUG_PRINT("received MTU final"); - this->_client._mtuFinalizedFlag = true; - this->_client.getStatus().resetTimeout(); - continue; - } - } - - //Checking commands - { - std::scoped_lock const commandLock(this->g_mutexCommands); - if (!this->g_commands.empty()) - { - - auto const result = - this->g_commands.front()->onReceive(packet, this->g_socket.getAddressType(), this->_client); - if (result == NetCommandResults::SUCCESS || result == NetCommandResults::FAILURE) - { - this->g_commands.pop_front(); - } - - //Commands can drop the packet - if (!packet) - { - continue; - } - } - } - - this->pushPacket(std::move(packet)); - this->g_receptionNotifier.notify_all(); - } - } -} -void ClientSideNetUdp::threadTransmission() -{ - auto lastTimePoint = std::chrono::steady_clock::now(); - std::chrono::milliseconds commandsTime{0}; - CompressorLZ4 compressor; - - std::unique_lock lckServer(this->_g_mutexFlux); - - while (this->g_running) - { - this->g_transmissionNotifier.wait_for(lckServer, std::chrono::milliseconds(10)); - - auto const now = std::chrono::steady_clock::now(); - auto const deltaTime = std::chrono::duration_cast(now - lastTimePoint); - lastTimePoint = now; - - //Checking commands - commandsTime += std::max(std::chrono::milliseconds{1}, deltaTime); - if (commandsTime >= FGE_NET_CMD_UPDATE_TICK_MS) - { - std::scoped_lock const commandLock(this->g_mutexCommands); - if (!this->g_commands.empty()) - { - TransmitPacketPtr possiblePacket; - auto const result = this->g_commands.front()->update(possiblePacket, this->g_socket.getAddressType(), - this->_client, commandsTime); - if (result == NetCommandResults::SUCCESS || result == NetCommandResults::FAILURE) - { - this->g_commands.pop_front(); - } - - if (possiblePacket) - { - //Pushing the packet to the client queue - this->_client.pushPacket(std::move(possiblePacket)); - } - } - - commandsTime = std::chrono::milliseconds::zero(); - } - - //Flux - if (this->_client.isPendingPacketsEmpty()) - { - continue; - } - - if (this->_client.getLastPacketLatency() >= this->_client.getCTOSLatency_ms()) - { //Ready to send ! - auto transmissionPacket = this->_client.popPacket(); - - //Compression and applying options - if (!transmissionPacket->isFragmented() && this->_client.getStatus().isInEncryptedState()) - { - transmissionPacket->applyOptions(this->_client); - if (!transmissionPacket->compress(compressor)) - { - continue; - } - } - else - { - transmissionPacket->applyOptions(this->_client); - } - - //MTU check - if (!transmissionPacket->isFragmented() && - !transmissionPacket->checkFlags(FGE_NET_HEADER_DO_NOT_FRAGMENT_FLAG)) - { - //Packet is not fragmented, we have to check is size - if (this->_client.getMTU() == 0) - { //We don't know the MTU yet - goto mtu_check_skip; - } - - auto fragments = transmissionPacket->fragment(this->_client.getMTU()); - for (std::size_t i = 0; i < fragments.size(); ++i) - { - if (i == 0) - { - transmissionPacket = std::move(fragments[i]); - } - else - { - this->_client.pushForcedFrontPacket(std::move(fragments[i])); - } - } - } - mtu_check_skip: - - if (!transmissionPacket->packet() || !transmissionPacket->haveCorrectHeaderSize()) - { //Last verification of the packet - continue; - } - - //Check if the packet is a disconnection packet - if (transmissionPacket->retrieveHeaderId().value() == NET_INTERNAL_ID_DISCONNECT) - { - this->_client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); - this->_client.getStatus().setTimeout(FGE_NET_STATUS_DEFAULT_TIMEOUT); - this->_client.clearPackets(); - } - - //Check if the packet must be encrypted - if (transmissionPacket->isMarkedForEncryption()) - { - if (!CryptEncrypt(this->_client, *transmissionPacket)) - { - continue; - } - } - - //Sending the packet - this->g_socket.send(transmissionPacket->packet()); - this->_client.resetLastPacketTimePoint(); - } - } -} - } // namespace fge::net From af56b1e5dfb2ae70f4e6b2c8311429b2034ce841 Mon Sep 17 00:00:00 2001 From: "GuillaumeG." Date: Tue, 14 Oct 2025 20:47:36 +0200 Subject: [PATCH 02/13] PacketDefragmentation: add copy/move semantic --- includes/FastEngine/network/C_protocol.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/includes/FastEngine/network/C_protocol.hpp b/includes/FastEngine/network/C_protocol.hpp index ce8535fc..1c6104ff 100644 --- a/includes/FastEngine/network/C_protocol.hpp +++ b/includes/FastEngine/network/C_protocol.hpp @@ -287,8 +287,13 @@ class PacketDefragmentation { public: PacketDefragmentation() = default; + PacketDefragmentation(PacketDefragmentation const& r) = delete; + PacketDefragmentation(PacketDefragmentation&& r) noexcept = default; ~PacketDefragmentation() = default; + PacketDefragmentation& operator=(PacketDefragmentation const& r) = delete; + PacketDefragmentation& operator=(PacketDefragmentation&& r) noexcept = default; + enum class Results { RETRIEVABLE, From d928690920f23586f029a050df1fc323ce0a6bce Mon Sep 17 00:00:00 2001 From: "GuillaumeG." Date: Sun, 9 Nov 2025 22:34:02 +0100 Subject: [PATCH 03/13] add new NetConnectHandlerCommand to move code from C_server.cpp and better control it, ClientList has no more Server only member, Move out CryptInfo struct (still thinking about moving it again), add FGE_SERVER_CLIENTS_MAP_GC_DELAY_MS, remove allowUnknownClient flag as now the default flux will always accept unknown clients and flux never, rename and make smarter notifyNewClient -> announceNewClient, --- .../server/main.cpp | 2 +- includes/FastEngine/network/C_client.hpp | 16 +- includes/FastEngine/network/C_clientList.hpp | 2 - includes/FastEngine/network/C_netCommand.hpp | 77 +++++- includes/FastEngine/network/C_server.hpp | 22 +- includes/private/fge_crypt.hpp | 14 +- sources/network/C_client.cpp | 16 +- sources/network/C_netClient.cpp | 5 +- sources/network/C_netCommand.cpp | 255 +++++++++++++++++- sources/network/C_netServer.cpp | 20 +- sources/network/C_server.cpp | 239 +++++----------- sources/private/fge_crypt.cpp | 33 ++- 12 files changed, 448 insertions(+), 253 deletions(-) diff --git a/examples/clientServerLifeSimulator_004/server/main.cpp b/examples/clientServerLifeSimulator_004/server/main.cpp index 862cd21f..6311dab8 100644 --- a/examples/clientServerLifeSimulator_004/server/main.cpp +++ b/examples/clientServerLifeSimulator_004/server/main.cpp @@ -212,7 +212,7 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) fge::net::ReceivedPacketPtr packet; fge::net::FluxProcessResults processResult; do { - processResult = serverFlux->process(client, packet, true); + processResult = serverFlux->process(client, packet); if (processResult != fge::net::FluxProcessResults::USER_RETRIEVABLE) { continue; diff --git a/includes/FastEngine/network/C_client.hpp b/includes/FastEngine/network/C_client.hpp index 9dbc40cc..ba02178b 100644 --- a/includes/FastEngine/network/C_client.hpp +++ b/includes/FastEngine/network/C_client.hpp @@ -212,6 +212,15 @@ class FGE_API ClientStatus std::chrono::steady_clock::time_point g_currentTimeout{std::chrono::steady_clock::now()}; }; +struct FGE_API CryptInfo +{ + ~CryptInfo(); + + void* _ssl{nullptr}; + void* _rbio{nullptr}; + void* _wbio{nullptr}; +}; + /** * \class Client * \brief Class that represent the identity of a client @@ -219,13 +228,6 @@ class FGE_API ClientStatus class FGE_API Client { public: - struct CryptInfo - { - void* _ssl{nullptr}; - void* _rbio{nullptr}; - void* _wbio{nullptr}; - }; - Client(); ~Client(); /** diff --git a/includes/FastEngine/network/C_clientList.hpp b/includes/FastEngine/network/C_clientList.hpp index 0555e06e..48910859 100644 --- a/includes/FastEngine/network/C_clientList.hpp +++ b/includes/FastEngine/network/C_clientList.hpp @@ -68,8 +68,6 @@ class FGE_API ClientList ClientSharedPtr _client; PacketDefragmentation _defragmentation; CommandQueue _commands; - - std::future _mtuFuture; }; using DataList = std::unordered_map; diff --git a/includes/FastEngine/network/C_netCommand.hpp b/includes/FastEngine/network/C_netCommand.hpp index 7cf2538a..1a3a41e4 100644 --- a/includes/FastEngine/network/C_netCommand.hpp +++ b/includes/FastEngine/network/C_netCommand.hpp @@ -97,8 +97,10 @@ class NetCommand [[nodiscard]] virtual std::chrono::milliseconds getTimeoutTarget() const; protected: - [[nodiscard]] virtual NetCommandResults - internalUpdate(TransmitPacketPtr& buffPacket, IpAddress::Types addressType, Client& client) = 0; + [[nodiscard]] virtual NetCommandResults internalUpdate(TransmitPacketPtr& buffPacket, + IpAddress::Types addressType, + Client& client, + std::chrono::milliseconds deltaTime) = 0; [[nodiscard]] virtual NetCommandResults timeout(Client& client); void resetTimeout(); @@ -108,16 +110,18 @@ class NetCommand std::chrono::milliseconds g_timeout{0}; }; -class FGE_API NetMTUCommand : public NetCommand +class FGE_API NetMTUCommand final : public NetCommand { public: using NetCommand::NetCommand; - ~NetMTUCommand() final = default; + ~NetMTUCommand() override = default; [[nodiscard]] NetCommandTypes getType() const override { return NetCommandTypes::DISCOVER_MTU; } - [[nodiscard]] NetCommandResults - internalUpdate(TransmitPacketPtr& buffPacket, IpAddress::Types addressType, Client& client) override; + [[nodiscard]] NetCommandResults internalUpdate(TransmitPacketPtr& buffPacket, + IpAddress::Types addressType, + Client& client, + std::chrono::milliseconds deltaTime) override; [[nodiscard]] NetCommandResults onReceive(std::unique_ptr& packet, IpAddress::Types addressType, Client& client) override; @@ -144,19 +148,21 @@ class FGE_API NetMTUCommand : public NetCommand } g_state{States::ASKING}; }; -class FGE_API NetConnectCommand : public NetCommand +class FGE_API NetConnectCommand final : public NetCommand { public: using NetCommand::NetCommand; - ~NetConnectCommand() final = default; + ~NetConnectCommand() override = default; void setVersioningString(std::string_view versioningString); [[nodiscard]] std::string const& getVersioningString() const; [[nodiscard]] NetCommandTypes getType() const override { return NetCommandTypes::CONNECT; } - [[nodiscard]] NetCommandResults - internalUpdate(TransmitPacketPtr& buffPacket, IpAddress::Types addressType, Client& client) override; + [[nodiscard]] NetCommandResults internalUpdate(TransmitPacketPtr& buffPacket, + IpAddress::Types addressType, + Client& client, + std::chrono::milliseconds deltaTime) override; [[nodiscard]] NetCommandResults onReceive(std::unique_ptr& packet, IpAddress::Types addressType, Client& client) override; @@ -189,16 +195,61 @@ class FGE_API NetConnectCommand : public NetCommand std::string g_versioningString; }; -class FGE_API NetDisconnectCommand : public NetCommand +class FGE_API NetConnectHandlerCommand final : public NetCommand { public: using NetCommand::NetCommand; - ~NetDisconnectCommand() final = default; + ~NetConnectHandlerCommand() override = default; [[nodiscard]] NetCommandTypes getType() const override { return NetCommandTypes::CONNECT; } + [[nodiscard]] NetCommandResults internalUpdate(TransmitPacketPtr& buffPacket, + IpAddress::Types addressType, + Client& client, + std::chrono::milliseconds deltaTime) override; [[nodiscard]] NetCommandResults - internalUpdate(TransmitPacketPtr& buffPacket, IpAddress::Types addressType, Client& client) override; + onReceive(std::unique_ptr& packet, IpAddress::Types addressType, Client& client) override; + + [[nodiscard]] inline std::future get_future() { return this->g_promise.get_future(); } + + [[nodiscard]] inline std::chrono::milliseconds getTimeoutTarget() const override + { + return FGE_NET_CONNECT_TIMEOUT_MS; + } + +private: + [[nodiscard]] NetCommandResults timeout(Client& client) override; + + std::promise g_promise; + enum class States + { + LOOKUP_MTU, + + DEALING_WITH_MTU, + WAITING_CLIENT_FINAL_MTU, + + CRYPT_HANDSHAKE, + CRYPT_WAITING, + + CONNECTED + } g_state{States::LOOKUP_MTU}; + + std::future g_mtuFuture; + NetMTUCommand g_mtuCommand{this->_g_commandQueue}; +}; + +class FGE_API NetDisconnectCommand final : public NetCommand +{ +public: + using NetCommand::NetCommand; + ~NetDisconnectCommand() override = default; + + [[nodiscard]] NetCommandTypes getType() const override { return NetCommandTypes::CONNECT; } + + [[nodiscard]] NetCommandResults internalUpdate(TransmitPacketPtr& buffPacket, + IpAddress::Types addressType, + Client& client, + std::chrono::milliseconds deltaTime) override; [[nodiscard]] NetCommandResults onReceive(std::unique_ptr& packet, IpAddress::Types addressType, Client& client) override; diff --git a/includes/FastEngine/network/C_server.hpp b/includes/FastEngine/network/C_server.hpp index f784492d..cb8fd839 100644 --- a/includes/FastEngine/network/C_server.hpp +++ b/includes/FastEngine/network/C_server.hpp @@ -43,6 +43,7 @@ } #define FGE_SERVER_PACKET_RECEPTION_TIMEOUT_MS 250 +#define FGE_SERVER_CLIENTS_MAP_GC_DELAY_MS 5000 namespace fge { @@ -87,7 +88,7 @@ enum class ReturnEvents class FGE_API NetFluxUdp { public: - NetFluxUdp() = default; + NetFluxUdp(bool defaultFlux); NetFluxUdp(NetFluxUdp const& r) = delete; NetFluxUdp(NetFluxUdp&& r) noexcept = delete; virtual ~NetFluxUdp(); @@ -104,6 +105,8 @@ class FGE_API NetFluxUdp void setMaxPackets(std::size_t n); [[nodiscard]] std::size_t getMaxPackets() const; + [[nodiscard]] bool isDefaultFlux() const; + protected: bool pushPacket(ReceivedPacketPtr&& fluxPck); void forcePushPacket(ReceivedPacketPtr fluxPck); @@ -119,6 +122,7 @@ class FGE_API NetFluxUdp private: std::size_t g_maxPackets = FGE_SERVER_DEFAULT_MAXPACKET; + bool g_isDefaultFlux{false}; friend class ServerSideNetUdp; }; @@ -126,15 +130,11 @@ class FGE_API NetFluxUdp class FGE_API ServerNetFluxUdp : public NetFluxUdp { public: - explicit ServerNetFluxUdp(ServerSideNetUdp& server) : - NetFluxUdp(), - g_server(&server) - {} + ServerNetFluxUdp(ServerSideNetUdp& server, bool defaultFlux); ~ServerNetFluxUdp() override = default; void processClients(); - [[nodiscard]] FluxProcessResults - process(ClientSharedPtr& refClient, ReceivedPacketPtr& packet, bool allowUnknownClient); + [[nodiscard]] FluxProcessResults process(ClientSharedPtr& refClient, ReceivedPacketPtr& packet); void disconnectAllClients(std::chrono::milliseconds delay = std::chrono::milliseconds(0)) const; @@ -162,12 +162,8 @@ class FGE_API ServerNetFluxUdp : public NetFluxUdp checkCommands(ClientSharedPtr const& refClient, CommandQueue& commands, ReceivedPacketPtr& packet); [[nodiscard]] FluxProcessResults processUnknownClient(ClientSharedPtr& refClient, ReceivedPacketPtr& packet); - [[nodiscard]] FluxProcessResults processAcknowledgedClient(ClientList::Data& refClientData, - ReceivedPacketPtr& packet); - [[nodiscard]] FluxProcessResults processMTUDiscoveredClient(ClientList::Data& refClientData, - ReceivedPacketPtr& packet); - ServerSideNetUdp* g_server{nullptr}; + ServerSideNetUdp* const g_server{nullptr}; std::chrono::milliseconds g_commandsUpdateTick{0}; std::chrono::steady_clock::time_point g_lastCommandUpdateTimePoint{std::chrono::steady_clock::now()}; }; @@ -243,7 +239,7 @@ class FGE_API ServerSideNetUdp void notifyTransmission(); [[nodiscard]] bool isRunning() const; - void notifyNewClient(Identity const& identity, ClientSharedPtr const& client); + [[nodiscard]] bool announceNewClient(Identity const& identity, ClientSharedPtr const& client); void sendTo(TransmitPacketPtr& pck, Client const& client, Identity const& id); void sendTo(TransmitPacketPtr& pck, Identity const& id); diff --git a/includes/private/fge_crypt.hpp b/includes/private/fge_crypt.hpp index a36aa594..56ad1de3 100644 --- a/includes/private/fge_crypt.hpp +++ b/includes/private/fge_crypt.hpp @@ -18,7 +18,13 @@ #define _FGE_FGE_CRYPT_HPP_INCLUDED #include "FastEngine/network/C_client.hpp" -#include "FastEngine/network/C_packet.hpp" + +namespace fge::net +{ + +class Packet; + +} // namespace fge::net namespace fge::priv { @@ -27,9 +33,9 @@ namespace fge::priv [[nodiscard]] bool CryptServerInit(void*& ctx); void CryptUninit(void*& ctx); -[[nodiscard]] bool CryptClientCreate(void* ctx, net::Client& client); -[[nodiscard]] bool CryptServerCreate(void* ctx, net::Client& client); -void CryptClientDestroy(net::Client& client); +[[nodiscard]] bool CryptClientCreate(void* ctx, net::CryptInfo& client); +[[nodiscard]] bool CryptServerCreate(void* ctx, net::CryptInfo& client); +void CryptClientDestroy(net::CryptInfo& client); [[nodiscard]] bool CryptEncrypt(net::Client& client, net::Packet& packet); [[nodiscard]] bool CryptDecrypt(net::Client& client, net::Packet& packet); diff --git a/sources/network/C_client.cpp b/sources/network/C_client.cpp index a37812be..243f9950 100644 --- a/sources/network/C_client.cpp +++ b/sources/network/C_client.cpp @@ -102,6 +102,13 @@ bool ClientStatus::isTimeout() const return false; } +//CryptInfo + +CryptInfo::~CryptInfo() +{ + priv::CryptClientDestroy(*this); +} + //Client Client::Client() : @@ -111,10 +118,7 @@ Client::Client() : g_lastPacketTimePoint(std::chrono::steady_clock::now()), g_lastRealmChangeTimePoint(std::chrono::steady_clock::now()) {} -Client::~Client() -{ - priv::CryptClientDestroy(*this); -} +Client::~Client() = default; Client::Client(Latency_ms CTOSLatency, Latency_ms STOCLatency) : g_correctorTimestamp(std::nullopt), g_CTOSLatency_ms(CTOSLatency), @@ -440,11 +444,11 @@ ClientStatus& Client::getStatus() return this->g_status; } -Client::CryptInfo const& Client::getCryptInfo() const +CryptInfo const& Client::getCryptInfo() const { return this->g_cryptInfo; } -Client::CryptInfo& Client::getCryptInfo() +CryptInfo& Client::getCryptInfo() { return this->g_cryptInfo; } diff --git a/sources/network/C_netClient.cpp b/sources/network/C_netClient.cpp index 925fd8a9..574e3fe8 100644 --- a/sources/network/C_netClient.cpp +++ b/sources/network/C_netClient.cpp @@ -28,6 +28,7 @@ namespace fge::net //ClientSideNetUdp ClientSideNetUdp::ClientSideNetUdp(IpAddress::Types addressType) : + NetFluxUdp(false), g_socket(addressType) { this->_client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); @@ -77,7 +78,7 @@ bool ClientSideNetUdp::start(Port bindPort, this->g_socket.close(); return false; } - if (!CryptClientCreate(this->g_crypt_ctx, this->_client)) + if (!CryptClientCreate(this->g_crypt_ctx, this->_client.getCryptInfo())) { this->g_socket.close(); return false; @@ -121,7 +122,7 @@ void ClientSideNetUdp::stop() this->_client.setCurrentRealm(FGE_NET_DEFAULT_REALM); this->_client.setCurrentPacketCounter(0); - CryptClientDestroy(this->_client); + CryptClientDestroy(this->_client.getCryptInfo()); CryptUninit(this->g_crypt_ctx); } } diff --git a/sources/network/C_netCommand.cpp b/sources/network/C_netCommand.cpp index c0a08183..f9777669 100644 --- a/sources/network/C_netCommand.cpp +++ b/sources/network/C_netCommand.cpp @@ -52,7 +52,7 @@ NetCommandResults NetCommand::update(TransmitPacketPtr& buffPacket, } } - return this->internalUpdate(buffPacket, addressType, client); + return this->internalUpdate(buffPacket, addressType, client, deltaTime); } std::chrono::milliseconds NetCommand::getTimeoutTarget() const @@ -72,7 +72,8 @@ void NetCommand::resetTimeout() //NetMTUCommand NetCommandResults NetMTUCommand::internalUpdate(TransmitPacketPtr& buffPacket, IpAddress::Types addressType, - [[maybe_unused]] Client& client) + [[maybe_unused]] Client& client, + [[maybe_unused]] std::chrono::milliseconds deltaTime) { switch (this->g_state) { @@ -272,7 +273,8 @@ std::string const& NetConnectCommand::getVersioningString() const NetCommandResults NetConnectCommand::internalUpdate(TransmitPacketPtr& buffPacket, [[maybe_unused]] IpAddress::Types addressType, - Client& client) + Client& client, + [[maybe_unused]] std::chrono::milliseconds deltaTime) { switch (this->g_state) { @@ -459,6 +461,7 @@ NetCommandResults NetConnectCommand::onReceive(std::unique_ptr& this->g_state = States::DEALING_WITH_MTU; } break; + case States::CRYPT_HANDSHAKE: case States::CRYPT_WAITING: { if (packet->retrieveHeaderId() != NET_INTERNAL_ID_CRYPT_HANDSHAKE) @@ -497,11 +500,255 @@ NetCommandResults NetConnectCommand::timeout(Client& client) return NetCommandResults::FAILURE; } +//NetConnectHandlerCommand + +NetCommandResults NetConnectHandlerCommand::internalUpdate(TransmitPacketPtr& buffPacket, + IpAddress::Types addressType, + Client& client, + std::chrono::milliseconds deltaTime) +{ + switch (this->g_state) + { + case States::LOOKUP_MTU: + client._mtuFinalizedFlag = false; + + //Determine if we need to do MTU discovery (from our side) + if (client.getMTU() == 0) + { + this->g_mtuFuture = this->g_mtuCommand.get_future(); + this->g_state = States::DEALING_WITH_MTU; + } + else + { + this->g_state = States::WAITING_CLIENT_FINAL_MTU; + } + break; + case States::DEALING_WITH_MTU: + { + auto result = this->g_mtuCommand.update(buffPacket, addressType, client, deltaTime); + + if (result == NetCommandResults::FAILURE) + { + client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); + this->g_promise.set_value(false); + return NetCommandResults::FAILURE; + } + //The MTU command doesn't return SUCCESS as it is handled in onReceive() + } + break; + case States::WAITING_CLIENT_FINAL_MTU: + if (!client._mtuFinalizedFlag) + { + return NetCommandResults::WORKING; + } + + FGE_DEBUG_PRINT("mtu finalized, starting crypt exchange"); + client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::MTU_DISCOVERED); + client.getStatus().setTimeout(FGE_NET_STATUS_DEFAULT_TIMEOUT); + this->resetTimeout(); + this->g_state = States::CRYPT_HANDSHAKE; + break; + case States::CRYPT_HANDSHAKE: + { + auto& info = client.getCryptInfo(); + + if (SSL_is_init_finished(static_cast(info._ssl)) == 1) + { + FGE_DEBUG_PRINT("TX CONNECTED"); + client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::CONNECTED); + client.getStatus().setTimeout(FGE_NET_STATUS_DEFAULT_CONNECTED_TIMEOUT); + client.setClientPacketCounter(0); + client.setCurrentPacketCounter(0); + this->g_promise.set_value(true); + this->g_state = States::CONNECTED; + return NetCommandResults::SUCCESS; + } + + auto const result = SSL_do_handshake(static_cast(info._ssl)); + if (result <= 0) + { + auto const err = SSL_get_error(static_cast(info._ssl), result); + + if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) + { + ERR_print_errors_fp(stderr); + client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); + this->g_promise.set_value(false); + return NetCommandResults::FAILURE; + } + } + + FGE_DEBUG_PRINT("check for transmit crypt"); + + this->resetTimeout(); + + // Check if OpenSSL has produced encrypted handshake data + auto const pendingSize = BIO_ctrl_pending(static_cast(info._wbio)); + if (pendingSize == 0) + { + FGE_DEBUG_PRINT("no crypt handshake to transmit"); + return NetCommandResults::WORKING; + } + + FGE_DEBUG_PRINT("transmitting crypt"); + buffPacket = CreatePacket(NET_INTERNAL_ID_CRYPT_HANDSHAKE); + + auto const packetStartDataPosition = buffPacket->doNotDiscard().getDataSize(); + buffPacket->append(pendingSize); + + auto const finalSize = + BIO_read(static_cast(info._wbio), buffPacket->getData() + packetStartDataPosition, pendingSize); + if (finalSize <= 0 || static_cast(finalSize) != pendingSize) + { + FGE_DEBUG_PRINT("failed crypt"); + client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); + this->g_promise.set_value(false); + return NetCommandResults::FAILURE; + } + + FGE_DEBUG_PRINT("waiting response"); + this->g_state = States::CRYPT_WAITING; + } + break; + case States::CRYPT_WAITING: + if (SSL_is_init_finished(static_cast(client.getCryptInfo()._ssl)) == 1) + { + FGE_DEBUG_PRINT("CONNECTED"); + client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::CONNECTED); + client.getStatus().setTimeout(FGE_NET_STATUS_DEFAULT_CONNECTED_TIMEOUT); + client.setClientPacketCounter(0); + client.setCurrentPacketCounter(0); + this->g_promise.set_value(true); + return NetCommandResults::SUCCESS; + } + break; + case States::CONNECTED: + return NetCommandResults::SUCCESS; + default: + break; + } + + return NetCommandResults::WORKING; +} + +NetCommandResults NetConnectHandlerCommand::onReceive(std::unique_ptr& packet, + [[maybe_unused]] IpAddress::Types addressType, + Client& client) +{ + switch (this->g_state) + { + case States::DEALING_WITH_MTU: + { + auto result = this->g_mtuCommand.onReceive(packet, addressType, client); + + if (result == NetCommandResults::FAILURE) + { + client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); + this->g_promise.set_value(false); + return NetCommandResults::FAILURE; + } + if (result == NetCommandResults::SUCCESS) + { + auto const mtu = this->g_mtuFuture.get(); + + client.setMTU(mtu); + FGE_DEBUG_PRINT("MTU discovery ok, now waiting for client to finish"); + + auto response = CreatePacket(NET_INTERNAL_ID_MTU_FINAL); + response->doNotDiscard().doNotReorder().doNotFragment(); + client.pushPacket(std::move(response)); + + client.getStatus().resetTimeout(); + this->resetTimeout(); + this->g_state = States::WAITING_CLIENT_FINAL_MTU; + } + + if (!packet) + { + return NetCommandResults::WORKING; + } + } + [[fallthrough]]; + case States::WAITING_CLIENT_FINAL_MTU: + //At this point, the client still need to do the MTU discovery + switch (packet->retrieveHeaderId().value()) + { + case NET_INTERNAL_ID_MTU_TEST: + { + auto response = CreatePacket(NET_INTERNAL_ID_MTU_TEST_RESPONSE); + response->doNotDiscard().doNotReorder(); + client.pushPacket(std::move(response)); + client.getStatus().resetTimeout(); + FGE_DEBUG_PRINT("received MTU test"); + packet.reset(); + break; + } + case NET_INTERNAL_ID_MTU_ASK: + { + auto response = CreatePacket(NET_INTERNAL_ID_MTU_ASK_RESPONSE); + response->doNotDiscard().doNotReorder() + << SocketUdp::retrieveAdapterMTUForDestination(packet->getIdentity()._ip).value_or(0); + client.pushPacket(std::move(response)); + client.getStatus().resetTimeout(); + FGE_DEBUG_PRINT("received MTU ask"); + packet.reset(); + break; + } + case NET_INTERNAL_ID_MTU_FINAL: //TODO: need a timeout for the MTU final + FGE_DEBUG_PRINT("received MTU final"); + client._mtuFinalizedFlag = true; + client.getStatus().resetTimeout(); + packet.reset(); + break; + default: + break; + } + break; + case States::CRYPT_HANDSHAKE: + case States::CRYPT_WAITING: + { + if (packet->retrieveHeaderId() != NET_INTERNAL_ID_CRYPT_HANDSHAKE) + { + return NetCommandResults::WORKING; + } + + std::unique_ptr packetOwned{std::move(packet)}; + + auto& info = client.getCryptInfo(); + + auto const readPos = packetOwned->getReadPos(); + BIO_write(static_cast(info._rbio), packetOwned->getData() + readPos, + packetOwned->getDataSize() - readPos); + + FGE_DEBUG_PRINT("Crypt: received some data"); + + this->resetTimeout(); + this->g_state = States::CRYPT_HANDSHAKE; + } + break; + case States::CONNECTED: + return NetCommandResults::SUCCESS; + default: + break; + } + + return NetCommandResults::WORKING; +} + +NetCommandResults NetConnectHandlerCommand::timeout(Client& client) +{ + FGE_DEBUG_PRINT("connect: timeout"); + client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); + this->g_promise.set_value(false); + return NetCommandResults::FAILURE; +} + //NetDisconnectCommand NetCommandResults NetDisconnectCommand::internalUpdate(TransmitPacketPtr& buffPacket, [[maybe_unused]] IpAddress::Types addressType, - Client& client) + Client& client, + [[maybe_unused]] std::chrono::milliseconds deltaTime) { if (client.getStatus().getNetworkStatus() == ClientStatus::NetworkStatus::DISCONNECTED) { diff --git a/sources/network/C_netServer.cpp b/sources/network/C_netServer.cpp index 99432eaa..2d76046e 100644 --- a/sources/network/C_netServer.cpp +++ b/sources/network/C_netServer.cpp @@ -30,7 +30,7 @@ namespace fge::net ServerSideNetUdp::ServerSideNetUdp(IpAddress::Types type) : g_threadReception(nullptr), g_threadTransmission(nullptr), - g_defaultFlux(*this), + g_defaultFlux(*this, true), g_socket(type), g_running(false) {} @@ -129,7 +129,7 @@ ServerNetFluxUdp* ServerSideNetUdp::newFlux() { std::scoped_lock const lock(this->g_mutexServer); - this->g_fluxes.push_back(std::make_unique(*this)); + this->g_fluxes.push_back(std::make_unique(*this, false)); return this->g_fluxes.back().get(); } ServerNetFluxUdp* ServerSideNetUdp::getFlux(std::size_t index) @@ -193,10 +193,20 @@ bool ServerSideNetUdp::isRunning() const return this->g_running; } -void ServerSideNetUdp::notifyNewClient(Identity const& identity, ClientSharedPtr const& client) +[[nodiscard]] bool ServerSideNetUdp::announceNewClient(Identity const& identity, ClientSharedPtr const& client) { std::scoped_lock const lock(this->g_mutexServer); - this->g_clientsMap[identity] = client; + + auto result = this->g_clientsMap.emplace(identity, client); + if (!result.second) + { + if (!result.first->second.expired()) + { + return result.first->second.lock() == client; + } + result.first->second = client; + } + return true; } void ServerSideNetUdp::sendTo(TransmitPacketPtr& pck, Client const& client, Identity const& id) @@ -313,7 +323,7 @@ void ServerSideNetUdp::threadReception() //"Garbage collection" of the clients map auto const now = std::chrono::steady_clock::now(); if (std::chrono::duration_cast(now - gcClientsMap) >= - std::chrono::milliseconds{5000}) + std::chrono::milliseconds{FGE_SERVER_CLIENTS_MAP_GC_DELAY_MS}) { gcClientsMap = now; diff --git a/sources/network/C_server.cpp b/sources/network/C_server.cpp index 4fd6c349..162d89d9 100644 --- a/sources/network/C_server.cpp +++ b/sources/network/C_server.cpp @@ -28,6 +28,9 @@ namespace fge::net { //ServerFluxUdp +NetFluxUdp::NetFluxUdp(bool defaultFlux) : + g_isDefaultFlux(defaultFlux) +{} NetFluxUdp::~NetFluxUdp() { this->clearPackets(); @@ -167,7 +170,17 @@ std::size_t NetFluxUdp::getMaxPackets() const return this->g_maxPackets; } +bool NetFluxUdp::isDefaultFlux() const +{ + return this->g_isDefaultFlux; +} + //ServerNetFluxUdp +ServerNetFluxUdp::ServerNetFluxUdp(ServerSideNetUdp& server, bool defaultFlux) : + NetFluxUdp(defaultFlux), + g_server(&server) +{} + void ServerNetFluxUdp::processClients() { auto const now = std::chrono::steady_clock::now(); @@ -256,8 +269,7 @@ void ServerNetFluxUdp::disconnectAllClients(std::chrono::milliseconds delay) con std::this_thread::sleep_for(delay); } -FluxProcessResults -ServerNetFluxUdp::process(ClientSharedPtr& refClient, ReceivedPacketPtr& packet, bool allowUnknownClient) +FluxProcessResults ServerNetFluxUdp::process(ClientSharedPtr& refClient, ReceivedPacketPtr& packet) { this->processClients(); @@ -284,8 +296,8 @@ ServerNetFluxUdp::process(ClientSharedPtr& refClient, ReceivedPacketPtr& packet, if (refClientData == nullptr) { //Unknown client - if (allowUnknownClient) - { + if (this->isDefaultFlux()) + { //Only allow unknown clients on the default flux return this->processUnknownClient(refClient, packet); } @@ -301,10 +313,31 @@ ServerNetFluxUdp::process(ClientSharedPtr& refClient, ReceivedPacketPtr& packet, return FluxProcessResults::INTERNALLY_DISCARDED; } + //Checking commands + { + auto& commands = refClientData->_commands; + if (!commands.empty()) + { + auto const result = commands.front()->onReceive(packet, this->g_server->getAddressType(), *refClient); + if (result == NetCommandResults::SUCCESS || result == NetCommandResults::FAILURE) + { + commands.pop_front(); + } + + //Commands can drop the packet + if (!packet) + { + return FluxProcessResults::INTERNALLY_HANDLED; + } + } + } + //Check if the client is in an acknowledged state if (status.getNetworkStatus() == ClientStatus::NetworkStatus::ACKNOWLEDGED) { - return this->processAcknowledgedClient(*refClientData, packet); + //We can't allow an unknown packet at this point + //TODO: error handler class + return FluxProcessResults::INTERNALLY_DISCARDED; } //Check if the packet is a fragment @@ -330,7 +363,9 @@ ServerNetFluxUdp::process(ClientSharedPtr& refClient, ReceivedPacketPtr& packet, //Check if the client is in an MTU discovered state if (status.getNetworkStatus() == ClientStatus::NetworkStatus::MTU_DISCOVERED) { - return this->processMTUDiscoveredClient(*refClientData, packet); + //We can't allow an unknown packet at this point + //TODO: error handler class + return FluxProcessResults::INTERNALLY_DISCARDED; } auto const stat = @@ -551,15 +586,20 @@ FluxProcessResults ServerNetFluxUdp::processUnknownClient(ClientSharedPtr& refCl refClient = std::make_shared(); refClient->getStatus().setNetworkStatus(ClientStatus::NetworkStatus::ACKNOWLEDGED); refClient->getStatus().setTimeout(FGE_NET_STATUS_DEFAULT_TIMEOUT); - this->_clients.add(packet->getIdentity(), refClient); - this->g_server->notifyNewClient(packet->getIdentity(), refClient); - //Add MTU command as this is required for the next state - auto* clientData = this->_clients.getData(packet->getIdentity()); - auto command = std::make_unique(&clientData->_commands); - clientData->_mtuFuture = command->get_future(); - //At this point commands should be empty - clientData->_commands.push_front(std::move(command)); + //Create crypt context + if (!CryptServerCreate(this->g_server->getCryptContext(), refClient->getCryptInfo())) + { + FGE_DEBUG_PRINT("CryptServerCreate failed"); + return FluxProcessResults::INTERNALLY_DISCARDED; + } + + if (!this->g_server->announceNewClient(packet->getIdentity(), refClient)) + { + FGE_DEBUG_PRINT("announceNewClient failed, identity in use"); + return FluxProcessResults::INTERNALLY_DISCARDED; + } + this->_clients.add(packet->getIdentity(), refClient); //Respond to the handshake auto responsePacket = CreatePacket(NET_INTERNAL_ID_FGE_HANDSHAKE); @@ -570,177 +610,18 @@ FluxProcessResults ServerNetFluxUdp::processUnknownClient(ClientSharedPtr& refCl this->_onClientAcknowledged.call(refClient, packet->getIdentity()); + //Add connect handler command as this is required for establishing the connection + auto* clientData = this->_clients.getData(packet->getIdentity()); + auto command = std::make_unique(&clientData->_commands); + //At this point commands should be empty + clientData->_commands.push_front(std::move(command)); + return FluxProcessResults::INTERNALLY_HANDLED; } //We can't process packets further for unknown clients return FluxProcessResults::INTERNALLY_DISCARDED; } -FluxProcessResults ServerNetFluxUdp::processAcknowledgedClient(ClientList::Data& refClientData, - ReceivedPacketPtr& packet) -{ - //At this point, the client still need to do the MTU discovery - switch (packet->retrieveHeaderId().value()) - { - case NET_INTERNAL_ID_MTU_TEST: - { - auto response = CreatePacket(NET_INTERNAL_ID_MTU_TEST_RESPONSE); - response->doNotDiscard().doNotReorder(); - refClientData._client->pushPacket(std::move(response)); - refClientData._client->getStatus().resetTimeout(); - FGE_DEBUG_PRINT("received MTU test"); - break; - } - case NET_INTERNAL_ID_MTU_ASK: - { - auto response = CreatePacket(NET_INTERNAL_ID_MTU_ASK_RESPONSE); - response->doNotDiscard().doNotReorder() - << SocketUdp::retrieveAdapterMTUForDestination(packet->getIdentity()._ip).value_or(0); - refClientData._client->pushPacket(std::move(response)); - refClientData._client->getStatus().resetTimeout(); - FGE_DEBUG_PRINT("received MTU ask"); - break; - } - case NET_INTERNAL_ID_MTU_FINAL: - FGE_DEBUG_PRINT("received MTU final"); - refClientData._client->_mtuFinalizedFlag = true; - refClientData._client->getStatus().resetTimeout(); - //Client have finished the MTU discovery, but we have to check if the server have finished too - if (refClientData._client->getMTU() != 0) - { - if (!CryptServerCreate(this->g_server->getCryptContext(), *refClientData._client)) - { - FGE_DEBUG_PRINT("CryptServerCreate failed"); - //Discard the packet and the client - this->_clients.remove(packet->getIdentity()); - this->_onClientDropped.call(refClientData._client, packet->getIdentity()); - return FluxProcessResults::INTERNALLY_DISCARDED; - } - FGE_DEBUG_PRINT("CryptServerCreate success, starting crypt exchange"); - refClientData._client->getStatus().setNetworkStatus(ClientStatus::NetworkStatus::MTU_DISCOVERED); - refClientData._client->getStatus().setTimeout(FGE_NET_STATUS_DEFAULT_TIMEOUT); - this->_onClientMTUDiscovered.call(refClientData._client, packet->getIdentity()); - } - break; - default: - { - auto const result = this->checkCommands(refClientData._client, refClientData._commands, packet); - if (result == NetCommandResults::FAILURE) - { - FGE_DEBUG_PRINT("Command failed"); - //Discard the packet and the client - this->_clients.remove(packet->getIdentity()); - this->_onClientDropped.call(refClientData._client, packet->getIdentity()); - return FluxProcessResults::INTERNALLY_DISCARDED; - } - - if (result == NetCommandResults::SUCCESS) - { - FGE_DEBUG_PRINT("Command success"); - refClientData._client->setMTU(refClientData._mtuFuture.get()); - - auto response = CreatePacket(NET_INTERNAL_ID_MTU_FINAL); - response->doNotDiscard().doNotReorder(); - refClientData._client->pushPacket(std::move(response)); - refClientData._client->getStatus().resetTimeout(); - - //Server have finished the MTU discovery, but we have to check if the client have finished too - if (refClientData._client->_mtuFinalizedFlag) - { - FGE_DEBUG_PRINT("mtu finalized"); - if (!CryptServerCreate(this->g_server->getCryptContext(), *refClientData._client)) - { - FGE_DEBUG_PRINT("CryptServerCreate failed"); - //Discard the packet and the client - this->_clients.remove(packet->getIdentity()); - this->_onClientDropped.call(refClientData._client, packet->getIdentity()); - return FluxProcessResults::INTERNALLY_DISCARDED; - } - - FGE_DEBUG_PRINT("CryptServerCreate success, starting crypt exchange"); - refClientData._client->getStatus().setNetworkStatus(ClientStatus::NetworkStatus::MTU_DISCOVERED); - refClientData._client->getStatus().setTimeout(FGE_NET_STATUS_DEFAULT_TIMEOUT); - this->_onClientMTUDiscovered.call(refClientData._client, packet->getIdentity()); - } - } - } - break; - } - //TODO: add a timeout for the MTU discovery - return FluxProcessResults::INTERNALLY_HANDLED; -} -FluxProcessResults ServerNetFluxUdp::processMTUDiscoveredClient(ClientList::Data& refClientData, - ReceivedPacketPtr& packet) -{ - //Check if the packet is a crypt handshake - if (packet->retrieveHeaderId().value() != NET_INTERNAL_ID_CRYPT_HANDSHAKE) - { - return FluxProcessResults::INTERNALLY_DISCARDED; - } - - auto& refClient = refClientData._client; - - FGE_DEBUG_PRINT("receiving crypt handshake"); - auto& info = refClient->getCryptInfo(); - - refClient->getStatus().resetTimeout(); - - auto const readPos = packet->getReadPos(); - BIO_write(static_cast(info._rbio), packet->getData() + readPos, packet->getDataSize() - readPos); - - auto const result = SSL_do_handshake(static_cast(info._ssl)); - if (result <= 0) - { - auto const err = SSL_get_error(static_cast(info._ssl), result); - - if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) - { - ERR_print_errors_fp(stderr); - refClient->getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); - this->_clients.remove(packet->getIdentity()); - this->_onClientDropped.call(refClient, packet->getIdentity()); - return FluxProcessResults::INTERNALLY_DISCARDED; - } - } - - FGE_DEBUG_PRINT("check for transmit crypt"); - // Check if OpenSSL has produced encrypted handshake data - auto const pendingSize = BIO_ctrl_pending(static_cast(info._wbio)); - if (pendingSize == 0) - { - FGE_DEBUG_PRINT("no crypt handshake to transmit"); - return FluxProcessResults::INTERNALLY_DISCARDED; - } - FGE_DEBUG_PRINT("transmitting crypt"); - auto response = CreatePacket(NET_INTERNAL_ID_CRYPT_HANDSHAKE); - auto const packetStartDataPosition = response->doNotDiscard().getDataSize(); - response->append(pendingSize); - auto const finalSize = - BIO_read(static_cast(info._wbio), response->getData() + packetStartDataPosition, pendingSize); - if (finalSize <= 0 || static_cast(finalSize) != pendingSize) - { - FGE_DEBUG_PRINT("failed crypt"); - refClient->getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); - this->_clients.remove(packet->getIdentity()); - this->_onClientDropped.call(refClient, packet->getIdentity()); - return FluxProcessResults::INTERNALLY_DISCARDED; - } - refClient->pushPacket(std::move(response)); - this->g_server->notifyTransmission(); - - if (SSL_is_init_finished(static_cast(info._ssl)) == 1) - { - FGE_DEBUG_PRINT("CONNECTED"); - refClient->getStatus().setNetworkStatus(ClientStatus::NetworkStatus::CONNECTED); - refClient->getStatus().setTimeout(FGE_NET_STATUS_DEFAULT_CONNECTED_TIMEOUT); - refClient->setClientPacketCounter(0); - refClient->setCurrentPacketCounter(0); - this->_onClientConnected.call(refClient, packet->getIdentity()); - return FluxProcessResults::INTERNALLY_HANDLED; - } - - return FluxProcessResults::INTERNALLY_HANDLED; -} bool ServerNetFluxUdp::verifyRealm(ClientSharedPtr const& refClient, ReceivedPacketPtr const& packet) { diff --git a/sources/private/fge_crypt.cpp b/sources/private/fge_crypt.cpp index 42803867..38a47bf0 100644 --- a/sources/private/fge_crypt.cpp +++ b/sources/private/fge_crypt.cpp @@ -15,6 +15,7 @@ */ #include "private/fge_crypt.hpp" +#include "FastEngine/network/C_packet.hpp" #include #include #include @@ -227,7 +228,7 @@ void CryptUninit(void*& ctx) } } -bool CryptClientCreate(void* ctx, net::Client& client) +bool CryptClientCreate(void* ctx, net::CryptInfo& client) { CryptClientDestroy(client); @@ -238,7 +239,7 @@ bool CryptClientCreate(void* ctx, net::Client& client) ERR_print_errors_fp(stderr); return false; } - client.getCryptInfo()._ssl = ssl; + client._ssl = ssl; BIO* rbio = BIO_new(BIO_s_mem()); BIO* wbio = BIO_new(BIO_s_mem()); @@ -247,8 +248,8 @@ bool CryptClientCreate(void* ctx, net::Client& client) ERR_print_errors_fp(stderr); return false; } - client.getCryptInfo()._rbio = rbio; - client.getCryptInfo()._wbio = wbio; + client._rbio = rbio; + client._wbio = wbio; // Tell the memory BIOs to return -1 when no data is available BIO_set_mem_eof_return(rbio, -1); @@ -266,7 +267,7 @@ bool CryptClientCreate(void* ctx, net::Client& client) return true; } -bool CryptServerCreate(void* ctx, net::Client& client) +bool CryptServerCreate(void* ctx, net::CryptInfo& client) { CryptClientDestroy(client); @@ -277,7 +278,7 @@ bool CryptServerCreate(void* ctx, net::Client& client) ERR_print_errors_fp(stderr); return false; } - client.getCryptInfo()._ssl = ssl; + client._ssl = ssl; BIO* rbio = BIO_new(BIO_s_mem()); BIO* wbio = BIO_new(BIO_s_mem()); @@ -286,8 +287,8 @@ bool CryptServerCreate(void* ctx, net::Client& client) ERR_print_errors_fp(stderr); return false; } - client.getCryptInfo()._rbio = rbio; - client.getCryptInfo()._wbio = wbio; + client._rbio = rbio; + client._wbio = wbio; // Tell the memory BIOs to return -1 when no data is available BIO_set_mem_eof_return(rbio, -1); @@ -305,20 +306,18 @@ bool CryptServerCreate(void* ctx, net::Client& client) return true; } -void CryptClientDestroy(net::Client& client) +void CryptClientDestroy(net::CryptInfo& client) { - auto& info = client.getCryptInfo(); - - if (info._ssl == nullptr) + if (client._ssl == nullptr) { return; } - SSL_shutdown(static_cast(info._ssl)); - SSL_free(static_cast(info._ssl)); - info._ssl = nullptr; - info._rbio = nullptr; - info._wbio = nullptr; + SSL_shutdown(static_cast(client._ssl)); + SSL_free(static_cast(client._ssl)); + client._ssl = nullptr; + client._rbio = nullptr; + client._wbio = nullptr; } bool CryptEncrypt(net::Client& client, net::Packet& packet) From 83f33e82d0f626ce11e7d8d58ee2f54a175b29c3 Mon Sep 17 00:00:00 2001 From: "GuillaumeG." Date: Fri, 14 Nov 2025 22:07:09 +0100 Subject: [PATCH 04/13] Unify the return result of NetCommand (onReceive() and update()), fix NetCommandTypes, remove unnecessary callback _onClientMTUDiscovered, remove unused checkCommands() private method, re-implement missing _onClientConnected call --- .../server/main.cpp | 7 +- includes/FastEngine/network/C_netCommand.hpp | 66 +++--- includes/FastEngine/network/C_server.hpp | 3 - sources/network/C_netClient.cpp | 8 +- sources/network/C_netCommand.cpp | 216 ++++++++++-------- sources/network/C_server.cpp | 27 +-- 6 files changed, 161 insertions(+), 166 deletions(-) diff --git a/examples/clientServerLifeSimulator_004/server/main.cpp b/examples/clientServerLifeSimulator_004/server/main.cpp index 6311dab8..59bddee7 100644 --- a/examples/clientServerLifeSimulator_004/server/main.cpp +++ b/examples/clientServerLifeSimulator_004/server/main.cpp @@ -138,6 +138,7 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) //Handling clients connection serverFlux->_onClientConnected.addLambda([](fge::net::ClientSharedPtr const& client, fge::net::Identity const& id) { client->getStatus().setTimeout(LIFESIM_TIME_TIMEOUT); + std::cout << "New user connected and now trying to authenticate : " << id.toString() << std::endl; }); //Handling clients return packet @@ -274,12 +275,10 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) transmissionPacket->packet() << true; transmissionPacket->doNotReorder(); - std::cout << "new user : " << packet->getIdentity().toString() << " connected !" + std::cout << "new user : " << packet->getIdentity().toString() << " authenticated !" << std::endl; - //Create the new client with the packet identity - //client = std::make_shared(); - //clients.add(packet->getIdentity(), client); + //Authenticate the new client client->getStatus().setNetworkStatus(fge::net::ClientStatus::NetworkStatus::AUTHENTICATED); //Pack data required by the LatencyPlanner in order to compute latency diff --git a/includes/FastEngine/network/C_netCommand.hpp b/includes/FastEngine/network/C_netCommand.hpp index 1a3a41e4..b4a78725 100644 --- a/includes/FastEngine/network/C_netCommand.hpp +++ b/includes/FastEngine/network/C_netCommand.hpp @@ -66,7 +66,8 @@ enum class NetCommandTypes { DISCOVER_MTU, CONNECT, - DISCONNECT + DISCONNECT, + CONNECT_HANDLER }; enum class NetCommandResults { @@ -91,23 +92,26 @@ class NetCommand IpAddress::Types addressType, Client& client, std::chrono::milliseconds deltaTime); - [[nodiscard]] virtual NetCommandResults - onReceive(std::unique_ptr& packet, IpAddress::Types addressType, Client& client) = 0; + virtual void onReceive(std::unique_ptr& packet, IpAddress::Types addressType, Client& client) = 0; [[nodiscard]] virtual std::chrono::milliseconds getTimeoutTarget() const; protected: - [[nodiscard]] virtual NetCommandResults internalUpdate(TransmitPacketPtr& buffPacket, - IpAddress::Types addressType, - Client& client, - std::chrono::milliseconds deltaTime) = 0; + virtual void internalUpdate(TransmitPacketPtr& buffPacket, + IpAddress::Types addressType, + Client& client, + std::chrono::milliseconds deltaTime) = 0; [[nodiscard]] virtual NetCommandResults timeout(Client& client); void resetTimeout(); + void markAsFailed(); + void markAsSucceeded(); + CommandQueue* _g_commandQueue{nullptr}; private: std::chrono::milliseconds g_timeout{0}; + NetCommandResults g_currentResultState{NetCommandResults::WORKING}; }; class FGE_API NetMTUCommand final : public NetCommand @@ -118,12 +122,11 @@ class FGE_API NetMTUCommand final : public NetCommand [[nodiscard]] NetCommandTypes getType() const override { return NetCommandTypes::DISCOVER_MTU; } - [[nodiscard]] NetCommandResults internalUpdate(TransmitPacketPtr& buffPacket, - IpAddress::Types addressType, - Client& client, - std::chrono::milliseconds deltaTime) override; - [[nodiscard]] NetCommandResults - onReceive(std::unique_ptr& packet, IpAddress::Types addressType, Client& client) override; + void internalUpdate(TransmitPacketPtr& buffPacket, + IpAddress::Types addressType, + Client& client, + std::chrono::milliseconds deltaTime) override; + void onReceive(std::unique_ptr& packet, IpAddress::Types addressType, Client& client) override; [[nodiscard]] inline std::future get_future() { return this->g_promise.get_future(); } @@ -159,12 +162,11 @@ class FGE_API NetConnectCommand final : public NetCommand [[nodiscard]] NetCommandTypes getType() const override { return NetCommandTypes::CONNECT; } - [[nodiscard]] NetCommandResults internalUpdate(TransmitPacketPtr& buffPacket, - IpAddress::Types addressType, - Client& client, - std::chrono::milliseconds deltaTime) override; - [[nodiscard]] NetCommandResults - onReceive(std::unique_ptr& packet, IpAddress::Types addressType, Client& client) override; + void internalUpdate(TransmitPacketPtr& buffPacket, + IpAddress::Types addressType, + Client& client, + std::chrono::milliseconds deltaTime) override; + void onReceive(std::unique_ptr& packet, IpAddress::Types addressType, Client& client) override; [[nodiscard]] inline std::future get_future() { return this->g_promise.get_future(); } @@ -201,14 +203,13 @@ class FGE_API NetConnectHandlerCommand final : public NetCommand using NetCommand::NetCommand; ~NetConnectHandlerCommand() override = default; - [[nodiscard]] NetCommandTypes getType() const override { return NetCommandTypes::CONNECT; } + [[nodiscard]] NetCommandTypes getType() const override { return NetCommandTypes::CONNECT_HANDLER; } - [[nodiscard]] NetCommandResults internalUpdate(TransmitPacketPtr& buffPacket, - IpAddress::Types addressType, - Client& client, - std::chrono::milliseconds deltaTime) override; - [[nodiscard]] NetCommandResults - onReceive(std::unique_ptr& packet, IpAddress::Types addressType, Client& client) override; + void internalUpdate(TransmitPacketPtr& buffPacket, + IpAddress::Types addressType, + Client& client, + std::chrono::milliseconds deltaTime) override; + void onReceive(std::unique_ptr& packet, IpAddress::Types addressType, Client& client) override; [[nodiscard]] inline std::future get_future() { return this->g_promise.get_future(); } @@ -244,14 +245,13 @@ class FGE_API NetDisconnectCommand final : public NetCommand using NetCommand::NetCommand; ~NetDisconnectCommand() override = default; - [[nodiscard]] NetCommandTypes getType() const override { return NetCommandTypes::CONNECT; } + [[nodiscard]] NetCommandTypes getType() const override { return NetCommandTypes::DISCONNECT; } - [[nodiscard]] NetCommandResults internalUpdate(TransmitPacketPtr& buffPacket, - IpAddress::Types addressType, - Client& client, - std::chrono::milliseconds deltaTime) override; - [[nodiscard]] NetCommandResults - onReceive(std::unique_ptr& packet, IpAddress::Types addressType, Client& client) override; + void internalUpdate(TransmitPacketPtr& buffPacket, + IpAddress::Types addressType, + Client& client, + std::chrono::milliseconds deltaTime) override; + void onReceive(std::unique_ptr& packet, IpAddress::Types addressType, Client& client) override; [[nodiscard]] inline std::future get_future() { return this->g_promise.get_future(); } diff --git a/includes/FastEngine/network/C_server.hpp b/includes/FastEngine/network/C_server.hpp index cb8fd839..09697f73 100644 --- a/includes/FastEngine/network/C_server.hpp +++ b/includes/FastEngine/network/C_server.hpp @@ -144,7 +144,6 @@ class FGE_API ServerNetFluxUdp : public NetFluxUdp CallbackHandler _onClientTimeout; CallbackHandler _onClientAcknowledged; - CallbackHandler _onClientMTUDiscovered; CallbackHandler _onClientConnected; CallbackHandler _onClientDisconnected; CallbackHandler _onClientDropped; @@ -158,8 +157,6 @@ class FGE_API ServerNetFluxUdp : public NetFluxUdp private: [[nodiscard]] bool verifyRealm(ClientSharedPtr const& refClient, ReceivedPacketPtr const& packet); - [[nodiscard]] NetCommandResults - checkCommands(ClientSharedPtr const& refClient, CommandQueue& commands, ReceivedPacketPtr& packet); [[nodiscard]] FluxProcessResults processUnknownClient(ClientSharedPtr& refClient, ReceivedPacketPtr& packet); diff --git a/sources/network/C_netClient.cpp b/sources/network/C_netClient.cpp index 574e3fe8..e83fd7c9 100644 --- a/sources/network/C_netClient.cpp +++ b/sources/network/C_netClient.cpp @@ -547,13 +547,7 @@ void ClientSideNetUdp::threadReception() std::scoped_lock const commandLock(this->g_mutexCommands); if (!this->g_commands.empty()) { - - auto const result = - this->g_commands.front()->onReceive(packet, this->g_socket.getAddressType(), this->_client); - if (result == NetCommandResults::SUCCESS || result == NetCommandResults::FAILURE) - { - this->g_commands.pop_front(); - } + this->g_commands.front()->onReceive(packet, this->g_socket.getAddressType(), this->_client); //Commands can drop the packet if (!packet) diff --git a/sources/network/C_netCommand.cpp b/sources/network/C_netCommand.cpp index f9777669..406dc7e1 100644 --- a/sources/network/C_netCommand.cpp +++ b/sources/network/C_netCommand.cpp @@ -31,6 +31,11 @@ NetCommandResults NetCommand::update(TransmitPacketPtr& buffPacket, Client& client, std::chrono::milliseconds deltaTime) { + if (this->g_currentResultState != NetCommandResults::WORKING) + { + return this->g_currentResultState; + } + this->g_timeout += deltaTime; if (this->g_timeout >= this->getTimeoutTarget()) { @@ -52,7 +57,8 @@ NetCommandResults NetCommand::update(TransmitPacketPtr& buffPacket, } } - return this->internalUpdate(buffPacket, addressType, client, deltaTime); + this->internalUpdate(buffPacket, addressType, client, deltaTime); + return NetCommandResults::WORKING; } std::chrono::milliseconds NetCommand::getTimeoutTarget() const @@ -69,11 +75,26 @@ void NetCommand::resetTimeout() this->g_timeout = std::chrono::milliseconds::zero(); } +void NetCommand::markAsFailed() +{ + if (this->g_currentResultState == NetCommandResults::WORKING) + { + this->g_currentResultState = NetCommandResults::FAILURE; + } +} +void NetCommand::markAsSucceeded() +{ + if (this->g_currentResultState == NetCommandResults::WORKING) + { + this->g_currentResultState = NetCommandResults::SUCCESS; + } +} + //NetMTUCommand -NetCommandResults NetMTUCommand::internalUpdate(TransmitPacketPtr& buffPacket, - IpAddress::Types addressType, - [[maybe_unused]] Client& client, - [[maybe_unused]] std::chrono::milliseconds deltaTime) +void NetMTUCommand::internalUpdate(TransmitPacketPtr& buffPacket, + IpAddress::Types addressType, + [[maybe_unused]] Client& client, + [[maybe_unused]] std::chrono::milliseconds deltaTime) { switch (this->g_state) { @@ -118,12 +139,10 @@ NetCommandResults NetMTUCommand::internalUpdate(TransmitPacketPtr& buffPacket, default: break; } - - return NetCommandResults::WORKING; } -NetCommandResults NetMTUCommand::onReceive(std::unique_ptr& packet, - IpAddress::Types addressType, - [[maybe_unused]] Client& client) +void NetMTUCommand::onReceive(std::unique_ptr& packet, + IpAddress::Types addressType, + [[maybe_unused]] Client& client) { switch (this->g_state) { @@ -139,7 +158,8 @@ NetCommandResults NetMTUCommand::onReceive(std::unique_ptr& pack //Invalid packet FGE_DEBUG_PRINT("MTU: Invalid packet"); this->g_promise.set_value(0); - return NetCommandResults::FAILURE; + this->markAsFailed(); + return; } FGE_DEBUG_PRINT("MTU: targetMTU: {}", targetMTU); @@ -164,7 +184,8 @@ NetCommandResults NetMTUCommand::onReceive(std::unique_ptr& pack { FGE_DEBUG_PRINT("MTU: currentMTU == maximumMTU"); this->g_promise.set_value(this->g_currentMTU); - return NetCommandResults::SUCCESS; + this->markAsSucceeded(); + return; } //Compute a new target MTU @@ -193,7 +214,6 @@ NetCommandResults NetMTUCommand::onReceive(std::unique_ptr& pack this->resetTimeout(); this->g_state = States::DISCOVER; - return NetCommandResults::WORKING; } break; case States::DISCOVER: @@ -208,7 +228,15 @@ NetCommandResults NetMTUCommand::onReceive(std::unique_ptr& pack { FGE_DEBUG_PRINT(this->g_currentMTU == 0 ? "MTU: discovery failed" : "MTU: discovery ok"); this->g_promise.set_value(this->g_currentMTU); - return this->g_currentMTU == 0 ? NetCommandResults::FAILURE : NetCommandResults::SUCCESS; + if (this->g_currentMTU == 0) + { + this->markAsFailed(); + } + else + { + this->markAsSucceeded(); + } + return; } this->g_targetMTU += this->g_intervalMTU; @@ -226,8 +254,6 @@ NetCommandResults NetMTUCommand::onReceive(std::unique_ptr& pack default: break; } - - return NetCommandResults::WORKING; } NetCommandResults NetMTUCommand::timeout([[maybe_unused]] Client& client) { @@ -271,10 +297,10 @@ std::string const& NetConnectCommand::getVersioningString() const return this->g_versioningString; } -NetCommandResults NetConnectCommand::internalUpdate(TransmitPacketPtr& buffPacket, - [[maybe_unused]] IpAddress::Types addressType, - Client& client, - [[maybe_unused]] std::chrono::milliseconds deltaTime) +void NetConnectCommand::internalUpdate(TransmitPacketPtr& buffPacket, + [[maybe_unused]] IpAddress::Types addressType, + Client& client, + [[maybe_unused]] std::chrono::milliseconds deltaTime) { switch (this->g_state) { @@ -301,7 +327,8 @@ NetCommandResults NetConnectCommand::internalUpdate(TransmitPacketPtr& buffPacke //MTU discovery failed client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); this->g_promise.set_value(false); - return NetCommandResults::FAILURE; + this->markAsFailed(); + return; } client.setMTU(mtu); FGE_DEBUG_PRINT("MTU discovery ok, now waiting for server to finish"); @@ -327,7 +354,7 @@ NetCommandResults NetConnectCommand::internalUpdate(TransmitPacketPtr& buffPacke { if (!client._mtuFinalizedFlag) { - return NetCommandResults::WORKING; + return; } FGE_DEBUG_PRINT("MTU finalized"); client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::MTU_DISCOVERED); @@ -349,7 +376,8 @@ NetCommandResults NetConnectCommand::internalUpdate(TransmitPacketPtr& buffPacke client.setCurrentPacketCounter(0); this->g_promise.set_value(true); this->g_state = States::CONNECTED; - return NetCommandResults::SUCCESS; + this->markAsSucceeded(); + return; } auto const result = SSL_do_handshake(static_cast(info._ssl)); @@ -362,7 +390,8 @@ NetCommandResults NetConnectCommand::internalUpdate(TransmitPacketPtr& buffPacke ERR_print_errors_fp(stderr); client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); this->g_promise.set_value(false); - return NetCommandResults::FAILURE; + this->markAsFailed(); + return; } } @@ -375,7 +404,7 @@ NetCommandResults NetConnectCommand::internalUpdate(TransmitPacketPtr& buffPacke if (pendingSize == 0) { FGE_DEBUG_PRINT("no crypt handshake to transmit"); - return NetCommandResults::WORKING; + return; } FGE_DEBUG_PRINT("transmitting crypt"); @@ -391,7 +420,8 @@ NetCommandResults NetConnectCommand::internalUpdate(TransmitPacketPtr& buffPacke FGE_DEBUG_PRINT("failed crypt"); client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); this->g_promise.set_value(false); - return NetCommandResults::FAILURE; + this->markAsFailed(); + return; } FGE_DEBUG_PRINT("waiting response"); @@ -407,21 +437,19 @@ NetCommandResults NetConnectCommand::internalUpdate(TransmitPacketPtr& buffPacke client.setClientPacketCounter(0); client.setCurrentPacketCounter(0); this->g_promise.set_value(true); - return NetCommandResults::SUCCESS; + this->markAsSucceeded(); } break; case States::CONNECTED: - return NetCommandResults::SUCCESS; + this->markAsSucceeded(); default: break; } - - return NetCommandResults::WORKING; } -NetCommandResults NetConnectCommand::onReceive(std::unique_ptr& packet, - [[maybe_unused]] IpAddress::Types addressType, - Client& client) +void NetConnectCommand::onReceive(std::unique_ptr& packet, + [[maybe_unused]] IpAddress::Types addressType, + Client& client) { switch (this->g_state) { @@ -429,7 +457,7 @@ NetCommandResults NetConnectCommand::onReceive(std::unique_ptr& { if (packet->retrieveHeaderId().value() != NET_INTERNAL_ID_FGE_HANDSHAKE) { - return NetCommandResults::WORKING; + return; } std::unique_ptr packetOwned{std::move(packet)}; @@ -442,7 +470,8 @@ NetCommandResults NetConnectCommand::onReceive(std::unique_ptr& FGE_DEBUG_PRINT("handshake failed"); client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); this->g_promise.set_value(false); - return NetCommandResults::FAILURE; + this->markAsFailed(); + return; } if (handshake != FGE_NET_HANDSHAKE_STRING) @@ -450,7 +479,8 @@ NetCommandResults NetConnectCommand::onReceive(std::unique_ptr& FGE_DEBUG_PRINT("handshake failed"); client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); this->g_promise.set_value(false); - return NetCommandResults::FAILURE; + this->markAsFailed(); + return; } FGE_DEBUG_PRINT("RX ACKNOWLEDGED"); @@ -466,7 +496,7 @@ NetCommandResults NetConnectCommand::onReceive(std::unique_ptr& { if (packet->retrieveHeaderId() != NET_INTERNAL_ID_CRYPT_HANDSHAKE) { - return NetCommandResults::WORKING; + return; } std::unique_ptr packetOwned{std::move(packet)}; @@ -484,12 +514,10 @@ NetCommandResults NetConnectCommand::onReceive(std::unique_ptr& } break; case States::CONNECTED: - return NetCommandResults::SUCCESS; + this->markAsSucceeded(); default: break; } - - return NetCommandResults::WORKING; } NetCommandResults NetConnectCommand::timeout(Client& client) @@ -502,10 +530,10 @@ NetCommandResults NetConnectCommand::timeout(Client& client) //NetConnectHandlerCommand -NetCommandResults NetConnectHandlerCommand::internalUpdate(TransmitPacketPtr& buffPacket, - IpAddress::Types addressType, - Client& client, - std::chrono::milliseconds deltaTime) +void NetConnectHandlerCommand::internalUpdate(TransmitPacketPtr& buffPacket, + IpAddress::Types addressType, + Client& client, + std::chrono::milliseconds deltaTime) { switch (this->g_state) { @@ -531,15 +559,29 @@ NetCommandResults NetConnectHandlerCommand::internalUpdate(TransmitPacketPtr& bu { client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); this->g_promise.set_value(false); - return NetCommandResults::FAILURE; + this->markAsFailed(); + } + else if (result == NetCommandResults::SUCCESS) + { + auto const mtu = this->g_mtuFuture.get(); + + client.setMTU(mtu); + FGE_DEBUG_PRINT("MTU discovery ok, now waiting for client to finish"); + + auto response = CreatePacket(NET_INTERNAL_ID_MTU_FINAL); + response->doNotDiscard().doNotReorder().doNotFragment(); + client.pushPacket(std::move(response)); + + client.getStatus().resetTimeout(); + this->resetTimeout(); + this->g_state = States::WAITING_CLIENT_FINAL_MTU; } - //The MTU command doesn't return SUCCESS as it is handled in onReceive() } break; case States::WAITING_CLIENT_FINAL_MTU: if (!client._mtuFinalizedFlag) { - return NetCommandResults::WORKING; + return; } FGE_DEBUG_PRINT("mtu finalized, starting crypt exchange"); @@ -561,7 +603,8 @@ NetCommandResults NetConnectHandlerCommand::internalUpdate(TransmitPacketPtr& bu client.setCurrentPacketCounter(0); this->g_promise.set_value(true); this->g_state = States::CONNECTED; - return NetCommandResults::SUCCESS; + this->markAsSucceeded(); + return; } auto const result = SSL_do_handshake(static_cast(info._ssl)); @@ -574,7 +617,8 @@ NetCommandResults NetConnectHandlerCommand::internalUpdate(TransmitPacketPtr& bu ERR_print_errors_fp(stderr); client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); this->g_promise.set_value(false); - return NetCommandResults::FAILURE; + this->markAsFailed(); + return; } } @@ -587,7 +631,7 @@ NetCommandResults NetConnectHandlerCommand::internalUpdate(TransmitPacketPtr& bu if (pendingSize == 0) { FGE_DEBUG_PRINT("no crypt handshake to transmit"); - return NetCommandResults::WORKING; + return; } FGE_DEBUG_PRINT("transmitting crypt"); @@ -603,7 +647,8 @@ NetCommandResults NetConnectHandlerCommand::internalUpdate(TransmitPacketPtr& bu FGE_DEBUG_PRINT("failed crypt"); client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); this->g_promise.set_value(false); - return NetCommandResults::FAILURE; + this->markAsFailed(); + return; } FGE_DEBUG_PRINT("waiting response"); @@ -619,53 +664,29 @@ NetCommandResults NetConnectHandlerCommand::internalUpdate(TransmitPacketPtr& bu client.setClientPacketCounter(0); client.setCurrentPacketCounter(0); this->g_promise.set_value(true); - return NetCommandResults::SUCCESS; + this->markAsSucceeded(); } break; case States::CONNECTED: - return NetCommandResults::SUCCESS; + this->markAsSucceeded(); default: break; } - - return NetCommandResults::WORKING; } -NetCommandResults NetConnectHandlerCommand::onReceive(std::unique_ptr& packet, - [[maybe_unused]] IpAddress::Types addressType, - Client& client) +void NetConnectHandlerCommand::onReceive(std::unique_ptr& packet, + [[maybe_unused]] IpAddress::Types addressType, + Client& client) { switch (this->g_state) { case States::DEALING_WITH_MTU: { - auto result = this->g_mtuCommand.onReceive(packet, addressType, client); - - if (result == NetCommandResults::FAILURE) - { - client.getStatus().setNetworkStatus(ClientStatus::NetworkStatus::DISCONNECTED); - this->g_promise.set_value(false); - return NetCommandResults::FAILURE; - } - if (result == NetCommandResults::SUCCESS) - { - auto const mtu = this->g_mtuFuture.get(); - - client.setMTU(mtu); - FGE_DEBUG_PRINT("MTU discovery ok, now waiting for client to finish"); - - auto response = CreatePacket(NET_INTERNAL_ID_MTU_FINAL); - response->doNotDiscard().doNotReorder().doNotFragment(); - client.pushPacket(std::move(response)); - - client.getStatus().resetTimeout(); - this->resetTimeout(); - this->g_state = States::WAITING_CLIENT_FINAL_MTU; - } + this->g_mtuCommand.onReceive(packet, addressType, client); if (!packet) { - return NetCommandResults::WORKING; + return; } } [[fallthrough]]; @@ -709,7 +730,7 @@ NetCommandResults NetConnectHandlerCommand::onReceive(std::unique_ptrretrieveHeaderId() != NET_INTERNAL_ID_CRYPT_HANDSHAKE) { - return NetCommandResults::WORKING; + return; } std::unique_ptr packetOwned{std::move(packet)}; @@ -727,12 +748,10 @@ NetCommandResults NetConnectHandlerCommand::onReceive(std::unique_ptrmarkAsSucceeded(); default: break; } - - return NetCommandResults::WORKING; } NetCommandResults NetConnectHandlerCommand::timeout(Client& client) @@ -745,20 +764,21 @@ NetCommandResults NetConnectHandlerCommand::timeout(Client& client) //NetDisconnectCommand -NetCommandResults NetDisconnectCommand::internalUpdate(TransmitPacketPtr& buffPacket, - [[maybe_unused]] IpAddress::Types addressType, - Client& client, - [[maybe_unused]] std::chrono::milliseconds deltaTime) +void NetDisconnectCommand::internalUpdate(TransmitPacketPtr& buffPacket, + [[maybe_unused]] IpAddress::Types addressType, + Client& client, + [[maybe_unused]] std::chrono::milliseconds deltaTime) { if (client.getStatus().getNetworkStatus() == ClientStatus::NetworkStatus::DISCONNECTED) { this->g_promise.set_value(); - return NetCommandResults::SUCCESS; + this->markAsSucceeded(); + return; } if (this->g_transmitted) { - return NetCommandResults::WORKING; + return; } client.clearPackets(); @@ -767,15 +787,13 @@ NetCommandResults NetDisconnectCommand::internalUpdate(TransmitPacketPtr& buffPa buffPacket->doNotDiscard().doNotReorder().doNotFragment(); this->g_transmitted = true; - return NetCommandResults::SUCCESS; + this->markAsSucceeded(); } -NetCommandResults NetDisconnectCommand::onReceive([[maybe_unused]] std::unique_ptr& packet, - [[maybe_unused]] IpAddress::Types addressType, - [[maybe_unused]] Client& client) -{ - return NetCommandResults::WORKING; -} +void NetDisconnectCommand::onReceive([[maybe_unused]] std::unique_ptr& packet, + [[maybe_unused]] IpAddress::Types addressType, + [[maybe_unused]] Client& client) +{} NetCommandResults NetDisconnectCommand::timeout([[maybe_unused]] Client& client) { diff --git a/sources/network/C_server.cpp b/sources/network/C_server.cpp index 162d89d9..73bf6de3 100644 --- a/sources/network/C_server.cpp +++ b/sources/network/C_server.cpp @@ -237,6 +237,7 @@ void ServerNetFluxUdp::processClients() if (!commands.empty()) { TransmitPacketPtr possiblePacket; + auto const type = commands.front()->getType(); auto result = commands.front()->update(possiblePacket, this->g_server->getAddressType(), *client, this->g_commandsUpdateTick); if (result == NetCommandResults::SUCCESS || result == NetCommandResults::FAILURE) @@ -250,6 +251,11 @@ void ServerNetFluxUdp::processClients() //Pushing the packet client->pushPacket(std::move(possiblePacket)); } + + if (result == NetCommandResults::SUCCESS && type == NetCommandTypes::CONNECT_HANDLER) + { + this->_onClientConnected.call(client, it->first); + } } } @@ -318,11 +324,7 @@ FluxProcessResults ServerNetFluxUdp::process(ClientSharedPtr& refClient, Receive auto& commands = refClientData->_commands; if (!commands.empty()) { - auto const result = commands.front()->onReceive(packet, this->g_server->getAddressType(), *refClient); - if (result == NetCommandResults::SUCCESS || result == NetCommandResults::FAILURE) - { - commands.pop_front(); - } + commands.front()->onReceive(packet, this->g_server->getAddressType(), *refClient); //Commands can drop the packet if (!packet) @@ -636,20 +638,5 @@ bool ServerNetFluxUdp::verifyRealm(ClientSharedPtr const& refClient, ReceivedPac } return true; } -NetCommandResults -ServerNetFluxUdp::checkCommands(ClientSharedPtr const& refClient, CommandQueue& commands, ReceivedPacketPtr& packet) -{ - if (commands.empty()) - { - return NetCommandResults::FAILURE; - } - - auto const result = commands.front()->onReceive(packet, this->g_server->getAddressType(), *refClient); - if (result == NetCommandResults::SUCCESS || result == NetCommandResults::FAILURE) - { - commands.pop_front(); - } - return result; -} } // namespace fge::net From d79df3a3ad526d7d0c3c78384877b54271b1058c Mon Sep 17 00:00:00 2001 From: "GuillaumeG." Date: Mon, 17 Nov 2025 20:13:02 +0100 Subject: [PATCH 05/13] create ClientContext for ClientList, move ClientListEvent in ClientList, free a bit Client class from complexity --- .../client/main.cpp | 2 +- includes/FastEngine/network/C_client.hpp | 7 --- includes/FastEngine/network/C_clientList.hpp | 51 ++++++++++++------- includes/FastEngine/network/C_server.hpp | 11 ++-- sources/C_scene.cpp | 2 +- sources/network/C_client.cpp | 19 +------ sources/network/C_clientList.cpp | 10 ++-- sources/network/C_netClient.cpp | 40 +++++++++------ sources/network/C_netServer.cpp | 12 ++--- sources/network/C_networkType.cpp | 2 +- sources/network/C_server.cpp | 44 ++++++++-------- 11 files changed, 99 insertions(+), 101 deletions(-) diff --git a/examples/clientServerLifeSimulator_004/client/main.cpp b/examples/clientServerLifeSimulator_004/client/main.cpp index 26ac628e..4509d893 100644 --- a/examples/clientServerLifeSimulator_004/client/main.cpp +++ b/examples/clientServerLifeSimulator_004/client/main.cpp @@ -275,7 +275,7 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) std::cout << "connection ok" << std::endl; server.enableReturnPacket(true); - server._client.getPacketReorderer().setMaximumSize(FGE_NET_PACKET_REORDERER_CACHE_COMPUTE( + server.getClientContext()._reorderer.setMaximumSize(FGE_NET_PACKET_REORDERER_CACHE_COMPUTE( FGE_NET_DEFAULT_RETURN_PACKET_RATE.count(), LIFESIM_SERVER_TICK)); auto transmissionPacket = fge::net::CreatePacket(ls::LS_PROTOCOL_C_PLEASE_CONNECT_ME); diff --git a/includes/FastEngine/network/C_client.hpp b/includes/FastEngine/network/C_client.hpp index ba02178b..1c4298fe 100644 --- a/includes/FastEngine/network/C_client.hpp +++ b/includes/FastEngine/network/C_client.hpp @@ -392,11 +392,6 @@ class FGE_API Client void resetLastReorderedPacketCounter(); [[nodiscard]] ProtocolPacket::CounterType getLastReorderedPacketCounter() const; - [[nodiscard]] PacketReorderer& getPacketReorderer(); - [[nodiscard]] PacketReorderer const& getPacketReorderer() const; - - [[nodiscard]] DataLockPair getPacketCache(); - [[nodiscard]] DataLockPair getPacketCache() const; void acknowledgeReception(ReceivedPacketPtr const& packet); [[nodiscard]] std::vector const& getAcknowledgedList() const; void clearAcknowledgedList(); @@ -442,8 +437,6 @@ class FGE_API Client ProtocolPacket::CounterType g_clientPacketCounter{0}; std::vector g_acknowledgedPackets; - PacketCache g_packetCache; - PacketReorderer g_packetReorderer; uint32_t g_lostPacketCount{0}; uint32_t g_lostPacketThreshold{FGE_NET_DEFAULT_lOST_PACKET_THRESHOLD}; diff --git a/includes/FastEngine/network/C_clientList.hpp b/includes/FastEngine/network/C_clientList.hpp index 48910859..c05fd1ed 100644 --- a/includes/FastEngine/network/C_clientList.hpp +++ b/includes/FastEngine/network/C_clientList.hpp @@ -34,21 +34,12 @@ class SocketUdp; using ClientSharedPtr = std::shared_ptr; -/** - * \struct ClientListEvent - * \ingroup network - * \brief Represents an event on the client list (client added, client removed, ...) - */ -struct ClientListEvent +struct ClientContext { - enum Events : uint8_t - { - CLEVT_DELCLIENT = 0, - CLEVT_NEWCLIENT - }; - - Events _event; - Identity _id; + PacketDefragmentation _defragmentation; + PacketCache _cache; + PacketReorderer _reorderer; + CommandQueue _commands; }; /** @@ -66,12 +57,34 @@ class FGE_API ClientList {} ClientSharedPtr _client; - PacketDefragmentation _defragmentation; - CommandQueue _commands; + ClientContext _context; + }; + + /** + * \struct Event + * \ingroup network + * \brief Represents an event on the client list (client added, client removed, ...) + */ + struct Event + { + enum class Types : uint8_t + { + EVT_DELCLIENT = 0, + EVT_NEWCLIENT + }; + using Types_t = std::underlying_type_t; + + inline Event(Types eventType, Identity const& clientId) : + _event(eventType), + _id(clientId) + {} + + Types _event; + Identity _id; }; using DataList = std::unordered_map; - using EventList = std::deque; + using EventList = std::deque; ClientList() = default; ~ClientList() = default; @@ -188,13 +201,13 @@ class FGE_API ClientList * * \param evt A client event */ - void pushClientEvent(ClientListEvent const& evt); + void pushClientEvent(Event const& evt); /** * \brief Get the client event with its index * * \return The client event */ - ClientListEvent const& getClientEvent(std::size_t index) const; + Event const& getClientEvent(std::size_t index) const; /** * \brief Get the number of client events * diff --git a/includes/FastEngine/network/C_server.hpp b/includes/FastEngine/network/C_server.hpp index 09697f73..73855ead 100644 --- a/includes/FastEngine/network/C_server.hpp +++ b/includes/FastEngine/network/C_server.hpp @@ -111,9 +111,10 @@ class FGE_API NetFluxUdp bool pushPacket(ReceivedPacketPtr&& fluxPck); void forcePushPacket(ReceivedPacketPtr fluxPck); void forcePushPacketFront(ReceivedPacketPtr fluxPck); - [[nodiscard]] FluxProcessResults processReorder(Client& client, + [[nodiscard]] FluxProcessResults processReorder(PacketReorderer& reorderer, ReceivedPacketPtr& packet, ProtocolPacket::CounterType currentCounter, + ProtocolPacket::RealmType clientRealm, bool ignoreRealm); mutable std::mutex _g_mutexFlux; @@ -305,6 +306,8 @@ class FGE_API ClientSideNetUdp : public NetFluxUdp [[nodiscard]] std::size_t waitForPackets(std::chrono::milliseconds time_ms); [[nodiscard]] Identity const& getClientIdentity() const; + [[nodiscard]] ClientContext const& getClientContext() const; + [[nodiscard]] ClientContext& getClientContext(); template void sendTo(TransmitPacketPtr& pck, Identity const& id); @@ -335,9 +338,6 @@ class FGE_API ClientSideNetUdp : public NetFluxUdp void threadReception(); void threadTransmission(); - std::recursive_mutex g_mutexCommands; - CommandQueue g_commands; - std::unique_ptr g_threadReception; std::unique_ptr g_threadTransmission; @@ -349,7 +349,8 @@ class FGE_API ClientSideNetUdp : public NetFluxUdp Identity g_clientIdentity; - PacketDefragmentation g_defragmentation; + std::recursive_mutex g_mutexCommands; + ClientContext g_clientContext; bool g_returnPacketEnabled{false}; TransmitPacketPtr g_returnPacket; diff --git a/sources/C_scene.cpp b/sources/C_scene.cpp index 5ae52bb5..26b92fc6 100644 --- a/sources/C_scene.cpp +++ b/sources/C_scene.cpp @@ -1579,7 +1579,7 @@ void Scene::clientsCheckup(fge::net::ClientList const& clients, bool force) for (std::size_t i = 0; i < clients.getClientEventSize(); ++i) { auto const& evt = clients.getClientEvent(i); - if (evt._event == fge::net::ClientListEvent::CLEVT_DELCLIENT) + if (evt._event == fge::net::ClientList::Event::Types::EVT_DELCLIENT) { this->g_perClientSyncs.erase(evt._id); } diff --git a/sources/network/C_client.cpp b/sources/network/C_client.cpp index 243f9950..627fc7c3 100644 --- a/sources/network/C_client.cpp +++ b/sources/network/C_client.cpp @@ -368,27 +368,10 @@ ProtocolPacket::CounterType Client::getLastReorderedPacketCounter() const return this->g_lastReorderedPacketCounter; } -PacketReorderer& Client::getPacketReorderer() -{ - return this->g_packetReorderer; -} -PacketReorderer const& Client::getPacketReorderer() const -{ - return this->g_packetReorderer; -} - -DataLockPair Client::getPacketCache() -{ - return DataLockPair(&this->g_packetCache, this->g_mutex); -} -DataLockPair Client::getPacketCache() const -{ - return DataLockPair(&this->g_packetCache, this->g_mutex); -} void Client::acknowledgeReception(ReceivedPacketPtr const& packet) { std::scoped_lock const lck(this->g_mutex); - this->g_acknowledgedPackets.push_back({packet->retrieveCounter().value(), packet->retrieveRealm().value()}); + this->g_acknowledgedPackets.emplace_back(packet->retrieveCounter().value(), packet->retrieveRealm().value()); } std::vector const& Client::getAcknowledgedList() const { diff --git a/sources/network/C_clientList.cpp b/sources/network/C_clientList.cpp index 9cc4cd40..37ed0e91 100644 --- a/sources/network/C_clientList.cpp +++ b/sources/network/C_clientList.cpp @@ -51,7 +51,7 @@ void ClientList::add(Identity const& id, ClientSharedPtr const& newClient) this->g_data.emplace(id, newClient); if (this->g_enableClientEventsFlag) { - this->g_events.push_back({ClientListEvent::CLEVT_NEWCLIENT, id}); + this->g_events.emplace_back(Event::Types::EVT_NEWCLIENT, id); } } void ClientList::remove(Identity const& id) @@ -60,7 +60,7 @@ void ClientList::remove(Identity const& id) this->g_data.erase(id); if (this->g_enableClientEventsFlag) { - this->g_events.push_back({ClientListEvent::CLEVT_DELCLIENT, id}); + this->g_events.emplace_back(Event::Types::EVT_DELCLIENT, id); } } ClientList::DataList::iterator ClientList::remove(DataList::const_iterator itPos, @@ -69,7 +69,7 @@ ClientList::DataList::iterator ClientList::remove(DataList::const_iterator itPos lock.throwIfDifferent(this->g_mutex); if (this->g_enableClientEventsFlag) { - this->g_events.push_back({ClientListEvent::CLEVT_DELCLIENT, itPos->first}); + this->g_events.emplace_back(Event::Types::EVT_DELCLIENT, itPos->first); } return this->g_data.erase(itPos); } @@ -147,13 +147,13 @@ bool ClientList::isWatchingEvent() const return this->g_enableClientEventsFlag; } -void ClientList::pushClientEvent(ClientListEvent const& evt) +void ClientList::pushClientEvent(Event const& evt) { std::scoped_lock const lck(this->g_mutex); this->g_events.push_back(evt); } -ClientListEvent const& ClientList::getClientEvent(std::size_t index) const +ClientList::Event const& ClientList::getClientEvent(std::size_t index) const { std::scoped_lock const lck(this->g_mutex); return this->g_events[index]; diff --git a/sources/network/C_netClient.cpp b/sources/network/C_netClient.cpp index e83fd7c9..febc84ad 100644 --- a/sources/network/C_netClient.cpp +++ b/sources/network/C_netClient.cpp @@ -149,11 +149,11 @@ std::future ClientSideNetUdp::retrieveMTU() throw Exception("Cannot retrieve MTU without a running client"); } - auto command = std::make_unique(&this->g_commands); + auto command = std::make_unique(&this->g_clientContext._commands); auto future = command->get_future(); std::scoped_lock const lock(this->g_mutexCommands); - this->g_commands.push_back(std::move(command)); + this->g_clientContext._commands.push_back(std::move(command)); return future; } @@ -164,12 +164,12 @@ std::future ClientSideNetUdp::connect(std::string_view versioningString) throw Exception("Cannot connect without a running client"); } - auto command = std::make_unique(&this->g_commands); + auto command = std::make_unique(&this->g_clientContext._commands); auto future = command->get_future(); command->setVersioningString(versioningString); std::scoped_lock const lock(this->g_mutexCommands); - this->g_commands.push_back(std::move(command)); + this->g_clientContext._commands.push_back(std::move(command)); return future; } @@ -185,11 +185,11 @@ std::future ClientSideNetUdp::disconnect() this->enableReturnPacket(false); - auto command = std::make_unique(&this->g_commands); + auto command = std::make_unique(&this->g_clientContext._commands); auto future = command->get_future(); std::scoped_lock const lock(this->g_mutexCommands); - this->g_commands.push_back(std::move(command)); + this->g_clientContext._commands.push_back(std::move(command)); return future; } @@ -198,6 +198,14 @@ Identity const& ClientSideNetUdp::getClientIdentity() const { return this->g_clientIdentity; } +ClientContext const& ClientSideNetUdp::getClientContext() const +{ + return this->g_clientContext; +} +ClientContext& ClientSideNetUdp::getClientContext() +{ + return this->g_clientContext; +} FluxProcessResults ClientSideNetUdp::process(ReceivedPacketPtr& packet) { @@ -276,7 +284,8 @@ FluxProcessResults ClientSideNetUdp::process(ReceivedPacketPtr& packet) if (!doNotReorder && !packet->isMarkedAsLocallyReordered()) { auto reorderResult = - this->processReorder(this->_client, packet, this->_client.getCurrentPacketCounter(), false); + this->processReorder(this->g_clientContext._reorderer, packet, this->_client.getCurrentPacketCounter(), + this->_client.getCurrentRealm(), false); if (reorderResult != FluxProcessResults::USER_RETRIEVABLE) { return reorderResult; @@ -483,10 +492,10 @@ void ClientSideNetUdp::threadReception() //Check if the packet is a fragment if (packet->isFragmented()) { - auto const result = this->g_defragmentation.process(std::move(packet)); + auto const result = this->g_clientContext._defragmentation.process(std::move(packet)); if (result._result == PacketDefragmentation::Results::RETRIEVABLE) { - packet = this->g_defragmentation.retrieve(result._id, this->g_clientIdentity); + packet = this->g_clientContext._defragmentation.retrieve(result._id, this->g_clientIdentity); } else { @@ -545,9 +554,10 @@ void ClientSideNetUdp::threadReception() //Checking commands { std::scoped_lock const commandLock(this->g_mutexCommands); - if (!this->g_commands.empty()) + if (!this->g_clientContext._commands.empty()) { - this->g_commands.front()->onReceive(packet, this->g_socket.getAddressType(), this->_client); + this->g_clientContext._commands.front()->onReceive(packet, this->g_socket.getAddressType(), + this->_client); //Commands can drop the packet if (!packet) @@ -583,14 +593,14 @@ void ClientSideNetUdp::threadTransmission() if (commandsTime >= FGE_NET_CMD_UPDATE_TICK_MS) { std::scoped_lock const commandLock(this->g_mutexCommands); - if (!this->g_commands.empty()) + if (!this->g_clientContext._commands.empty()) { TransmitPacketPtr possiblePacket; - auto const result = this->g_commands.front()->update(possiblePacket, this->g_socket.getAddressType(), - this->_client, commandsTime); + auto const result = this->g_clientContext._commands.front()->update( + possiblePacket, this->g_socket.getAddressType(), this->_client, commandsTime); if (result == NetCommandResults::SUCCESS || result == NetCommandResults::FAILURE) { - this->g_commands.pop_front(); + this->g_clientContext._commands.pop_front(); } if (possiblePacket) diff --git a/sources/network/C_netServer.cpp b/sources/network/C_netServer.cpp index 2d76046e..35593160 100644 --- a/sources/network/C_netServer.cpp +++ b/sources/network/C_netServer.cpp @@ -367,21 +367,20 @@ void ServerSideNetUdp::threadTransmission() for (auto itClient = clients->begin(clientLock); itClient != clients->end(clientLock); ++itClient) { - auto& client = itClient->second._client; + auto& clientData = itClient->second; + auto& client = clientData._client; //check cache { - auto const packetCache = client->getPacketCache(); - auto const clientLatency = client->getPacketReturnRate() * FGE_NET_PACKET_CACHE_DELAY_FACTOR + std::chrono::milliseconds(client->_latencyPlanner.getRoundTripTime().value_or(1)); - while (packetCache.first->check( + while (clientData._context._cache.check( timePoint, std::chrono::duration_cast(clientLatency))) { FGE_DEBUG_PRINT("re-transmit packet as client didn't acknowledge it"); - client->pushForcedFrontPacket(packetCache.first->pop()); + client->pushForcedFrontPacket(clientData._context._cache.pop()); } } @@ -407,8 +406,7 @@ void ServerSideNetUdp::threadTransmission() { continue; } - auto const packetCache = client->getPacketCache(); - packetCache.first->push(transmissionPacket); + clientData._context._cache.push(transmissionPacket); } else { diff --git a/sources/network/C_networkType.cpp b/sources/network/C_networkType.cpp index 83c29ad7..d0e8d9b6 100644 --- a/sources/network/C_networkType.cpp +++ b/sources/network/C_networkType.cpp @@ -168,7 +168,7 @@ void PerClientSyncContext::clientsCheckup(ClientList const& clients, { auto const& evt = clients.getClientEvent(i); - if (evt._event == ClientListEvent::CLEVT_DELCLIENT) + if (evt._event == ClientList::Event::Types::EVT_DELCLIENT) { this->delClient(evt._id); } diff --git a/sources/network/C_server.cpp b/sources/network/C_server.cpp index 73bf6de3..cad613d4 100644 --- a/sources/network/C_server.cpp +++ b/sources/network/C_server.cpp @@ -63,14 +63,15 @@ void NetFluxUdp::forcePushPacketFront(ReceivedPacketPtr fluxPck) this->_g_packets.push_front(std::move(fluxPck)); } -FluxProcessResults NetFluxUdp::processReorder(Client& client, +FluxProcessResults NetFluxUdp::processReorder(PacketReorderer& reorderer, ReceivedPacketPtr& packet, ProtocolPacket::CounterType currentCounter, + ProtocolPacket::RealmType clientRealm, bool ignoreRealm) { - auto currentRealm = ignoreRealm ? packet->retrieveRealm().value() : client.getCurrentRealm(); + auto currentRealm = ignoreRealm ? packet->retrieveRealm().value() : clientRealm; - if (client.getPacketReorderer().isEmpty()) + if (reorderer.isEmpty()) { auto const stat = PacketReorderer::checkStat(packet, currentCounter, currentRealm); @@ -80,40 +81,40 @@ FluxProcessResults NetFluxUdp::processReorder(Client& client, return FluxProcessResults::USER_RETRIEVABLE; } - client.getPacketReorderer().push(std::move(packet)); + reorderer.push(std::move(packet)); return FluxProcessResults::INTERNALLY_HANDLED; } //We push the packet in the reorderer - client.getPacketReorderer().push(std::move(packet)); + reorderer.push(std::move(packet)); //At this point we are sure that the reorderer contains at least 2 packets - bool forced = client.getPacketReorderer().isForced(); - auto const stat = client.getPacketReorderer().checkStat(currentCounter, currentRealm).value(); + bool forced = reorderer.isForced(); + auto const stat = reorderer.checkStat(currentCounter, currentRealm).value(); if (!forced && stat != PacketReorderer::Stats::RETRIEVABLE) { return FluxProcessResults::INTERNALLY_HANDLED; } - packet = client.getPacketReorderer().pop(); + packet = reorderer.pop(); packet->markAsLocallyReordered(); currentCounter = packet->retrieveCounter().value(); currentRealm = packet->retrieveRealm().value(); - auto const packerReordererMaxSize = client.getPacketReorderer().getMaximumSize(); + auto const packerReordererMaxSize = reorderer.getMaximumSize(); std::size_t containerInversedSize = 0; auto* containerInversed = FGE_ALLOCA_T(ReceivedPacketPtr, packerReordererMaxSize); FGE_PLACE_CONSTRUCT(ReceivedPacketPtr, packerReordererMaxSize, containerInversed); - while (auto const stat = client.getPacketReorderer().checkStat(currentCounter, currentRealm)) + while (auto const stat = reorderer.checkStat(currentCounter, currentRealm)) { if (!(stat == PacketReorderer::Stats::RETRIEVABLE || forced)) { break; } - auto reorderedPacket = client.getPacketReorderer().pop(); + auto reorderedPacket = reorderer.pop(); //Mark them as reordered reorderedPacket->markAsLocallyReordered(); @@ -233,7 +234,7 @@ void ServerNetFluxUdp::processClients() //Handle commands if (this->g_commandsUpdateTick >= FGE_NET_CMD_UPDATE_TICK_MS) { //TODO: move it to the transmit thread ? - auto& commands = it->second._commands; + auto& commands = it->second._context._commands; if (!commands.empty()) { TransmitPacketPtr possiblePacket; @@ -321,7 +322,7 @@ FluxProcessResults ServerNetFluxUdp::process(ClientSharedPtr& refClient, Receive //Checking commands { - auto& commands = refClientData->_commands; + auto& commands = refClientData->_context._commands; if (!commands.empty()) { commands.front()->onReceive(packet, this->g_server->getAddressType(), *refClient); @@ -346,10 +347,10 @@ FluxProcessResults ServerNetFluxUdp::process(ClientSharedPtr& refClient, Receive if (packet->isFragmented()) { auto const identity = packet->getIdentity(); - auto const result = refClientData->_defragmentation.process(std::move(packet)); + auto const result = refClientData->_context._defragmentation.process(std::move(packet)); if (result._result == PacketDefragmentation::Results::RETRIEVABLE) { - packet = refClientData->_defragmentation.retrieve(result._id, identity); + packet = refClientData->_context._defragmentation.retrieve(result._id, identity); } else { @@ -392,7 +393,9 @@ FluxProcessResults ServerNetFluxUdp::process(ClientSharedPtr& refClient, Receive bool const doNotReorder = packet->checkFlags(FGE_NET_HEADER_DO_NOT_REORDER_FLAG); if (!doNotReorder && !packet->isMarkedAsLocallyReordered()) { - auto reorderResult = this->processReorder(*refClient, packet, refClient->getClientPacketCounter(), true); + auto reorderResult = + this->processReorder(refClientData->_context._reorderer, packet, refClient->getClientPacketCounter(), + refClient->getCurrentRealm(), true); if (reorderResult != FluxProcessResults::USER_RETRIEVABLE) { return reorderResult; @@ -532,10 +535,7 @@ FluxProcessResults ServerNetFluxUdp::process(ClientSharedPtr& refClient, Receive acknowledgedPackets[i] = label; } - { - auto const packetCache = refClient->getPacketCache(); - packetCache.first->acknowledgeReception(acknowledgedPackets); - } + refClientData->_context._cache.acknowledgeReception(acknowledgedPackets); this->_onClientReturnPacket.call(refClient, packet->getIdentity(), packet); @@ -614,9 +614,9 @@ FluxProcessResults ServerNetFluxUdp::processUnknownClient(ClientSharedPtr& refCl //Add connect handler command as this is required for establishing the connection auto* clientData = this->_clients.getData(packet->getIdentity()); - auto command = std::make_unique(&clientData->_commands); + auto command = std::make_unique(&clientData->_context._commands); //At this point commands should be empty - clientData->_commands.push_front(std::move(command)); + clientData->_context._commands.push_front(std::move(command)); return FluxProcessResults::INTERNALLY_HANDLED; } From a3b2e4362de890386f72fb5e67091c7160fa547f Mon Sep 17 00:00:00 2001 From: "GuillaumeG." Date: Tue, 25 Nov 2025 19:59:32 +0100 Subject: [PATCH 06/13] make the Client acknowledgedPackets a unordered_set --- includes/FastEngine/network/C_client.hpp | 5 +++-- sources/network/C_client.cpp | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/includes/FastEngine/network/C_client.hpp b/includes/FastEngine/network/C_client.hpp index 1c4298fe..e6da86b7 100644 --- a/includes/FastEngine/network/C_client.hpp +++ b/includes/FastEngine/network/C_client.hpp @@ -28,6 +28,7 @@ #include #include #include +#include #define FGE_NET_DEFAULT_LATENCY 20 #define FGE_NET_CLIENT_TIMESTAMP_MODULO 65536 @@ -393,7 +394,7 @@ class FGE_API Client [[nodiscard]] ProtocolPacket::CounterType getLastReorderedPacketCounter() const; void acknowledgeReception(ReceivedPacketPtr const& packet); - [[nodiscard]] std::vector const& getAcknowledgedList() const; + [[nodiscard]] std::unordered_set const& getAcknowledgedList() const; void clearAcknowledgedList(); void clearLostPacketCount(); @@ -436,7 +437,7 @@ class FGE_API Client ProtocolPacket::CounterType g_lastReorderedPacketCounter{0}; ProtocolPacket::CounterType g_clientPacketCounter{0}; - std::vector g_acknowledgedPackets; + std::unordered_set g_acknowledgedPackets; uint32_t g_lostPacketCount{0}; uint32_t g_lostPacketThreshold{FGE_NET_DEFAULT_lOST_PACKET_THRESHOLD}; diff --git a/sources/network/C_client.cpp b/sources/network/C_client.cpp index 627fc7c3..150eaed6 100644 --- a/sources/network/C_client.cpp +++ b/sources/network/C_client.cpp @@ -371,9 +371,9 @@ ProtocolPacket::CounterType Client::getLastReorderedPacketCounter() const void Client::acknowledgeReception(ReceivedPacketPtr const& packet) { std::scoped_lock const lck(this->g_mutex); - this->g_acknowledgedPackets.emplace_back(packet->retrieveCounter().value(), packet->retrieveRealm().value()); + this->g_acknowledgedPackets.emplace(packet->retrieveCounter().value(), packet->retrieveRealm().value()); } -std::vector const& Client::getAcknowledgedList() const +std::unordered_set const& Client::getAcknowledgedList() const { return this->g_acknowledgedPackets; } From 5d377098c244c8d8661f5e8d3770fa3323890186 Mon Sep 17 00:00:00 2001 From: "GuillaumeG." Date: Tue, 25 Nov 2025 20:10:27 +0100 Subject: [PATCH 07/13] add PacketCache::Hash, PacketCache: add a enable flag --- includes/FastEngine/network/C_protocol.hpp | 14 ++++++++++++++ sources/network/C_protocol.cpp | 13 +++++++++++++ 2 files changed, 27 insertions(+) diff --git a/includes/FastEngine/network/C_protocol.hpp b/includes/FastEngine/network/C_protocol.hpp index 1c6104ff..1b112266 100644 --- a/includes/FastEngine/network/C_protocol.hpp +++ b/includes/FastEngine/network/C_protocol.hpp @@ -425,6 +425,17 @@ class FGE_API PacketCache { return this->_counter == r._counter && this->_realm == r._realm; } + + struct Hash + { + [[nodiscard]] inline std::size_t operator()(Label const& label) const + { + static_assert(sizeof(label._counter) == sizeof(label._realm) && sizeof(label._counter) == 2, + "ProtocolPacket::CounterType and ProtocolPacket::RealmType must be 16 bits"); + return std::hash()(static_cast(label._counter) << 16 | + static_cast(label._realm)); + } + }; }; PacketCache() = default; @@ -437,6 +448,8 @@ class FGE_API PacketCache void clear(); [[nodiscard]] bool isEmpty() const; + [[nodiscard]] bool isEnabled() const; + void enable(bool enable); //Transmit void push(TransmitPacketPtr const& packet); @@ -466,6 +479,7 @@ class FGE_API PacketCache std::vector g_cache{FGE_NET_PACKET_CACHE_MAX}; std::size_t g_start{0}; std::size_t g_end{0}; + bool g_enable{false}; }; } // namespace fge::net diff --git a/sources/network/C_protocol.cpp b/sources/network/C_protocol.cpp index 22091a9a..227ed140 100644 --- a/sources/network/C_protocol.cpp +++ b/sources/network/C_protocol.cpp @@ -423,9 +423,22 @@ bool PacketCache::isEmpty() const { return this->g_start == this->g_end; } +bool PacketCache::isEnabled() const +{ + return this->g_enable; +} +void PacketCache::enable(bool enable) +{ + this->g_enable = enable; +} void PacketCache::push(TransmitPacketPtr const& packet) { + if (!this->g_enable) + { + return; + } + auto const next = (this->g_end + 1) % FGE_NET_PACKET_CACHE_MAX; if (next == this->g_start) From fbbe3b8df17b1d478f68c347c96c0ad5d484a198 Mon Sep 17 00:00:00 2001 From: "GuillaumeG." Date: Thu, 27 Nov 2025 21:52:52 +0100 Subject: [PATCH 08/13] PacketCache add mutex --- includes/FastEngine/network/C_protocol.hpp | 7 ++-- sources/network/C_protocol.cpp | 42 ++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/includes/FastEngine/network/C_protocol.hpp b/includes/FastEngine/network/C_protocol.hpp index 1b112266..756e6ece 100644 --- a/includes/FastEngine/network/C_protocol.hpp +++ b/includes/FastEngine/network/C_protocol.hpp @@ -440,11 +440,11 @@ class FGE_API PacketCache PacketCache() = default; PacketCache(PacketCache const& r) = delete; - PacketCache(PacketCache&& r) noexcept = default; + PacketCache(PacketCache&& r) noexcept; ~PacketCache() = default; PacketCache& operator=(PacketCache const& r) = delete; - PacketCache& operator=(PacketCache&& r) noexcept = default; + PacketCache& operator=(PacketCache&& r) noexcept; void clear(); [[nodiscard]] bool isEmpty() const; @@ -475,10 +475,13 @@ class FGE_API PacketCache std::chrono::steady_clock::time_point _time{}; }; + mutable std::mutex g_mutex; + //Circular buffer std::vector g_cache{FGE_NET_PACKET_CACHE_MAX}; std::size_t g_start{0}; std::size_t g_end{0}; + bool g_enable{false}; }; diff --git a/sources/network/C_protocol.cpp b/sources/network/C_protocol.cpp index 227ed140..e62c7b89 100644 --- a/sources/network/C_protocol.cpp +++ b/sources/network/C_protocol.cpp @@ -411,8 +411,40 @@ PacketReorderer::Stats PacketReorderer::Data::checkStat(ProtocolPacket::CounterT //PacketCache +PacketCache::PacketCache(PacketCache&& r) noexcept : + PacketCache() +{ + std::scoped_lock const lockThis(this->g_mutex); + std::scoped_lock const lockR(r.g_mutex); + + this->g_cache = std::move(r.g_cache); + this->g_start = r.g_start; + this->g_end = r.g_end; + this->g_enable = r.g_enable; + + r.clear(); +} + +PacketCache& PacketCache::operator=(PacketCache&& r) noexcept +{ + if (this != &r) + { + std::scoped_lock const lockThis(this->g_mutex); + std::scoped_lock const lockR(r.g_mutex); + + this->g_cache = std::move(r.g_cache); + this->g_start = r.g_start; + this->g_end = r.g_end; + this->g_enable = r.g_enable; + + r.clear(); + } + return *this; +} + void PacketCache::clear() { + std::scoped_lock const lock(this->g_mutex); this->g_cache.clear(); this->g_cache.resize(FGE_NET_PACKET_CACHE_MAX); this->g_start = 0; @@ -421,19 +453,23 @@ void PacketCache::clear() bool PacketCache::isEmpty() const { + std::scoped_lock const lock(this->g_mutex); return this->g_start == this->g_end; } bool PacketCache::isEnabled() const { + std::scoped_lock const lock(this->g_mutex); return this->g_enable; } void PacketCache::enable(bool enable) { + std::scoped_lock const lock(this->g_mutex); this->g_enable = enable; } void PacketCache::push(TransmitPacketPtr const& packet) { + std::scoped_lock const lock(this->g_mutex); if (!this->g_enable) { return; @@ -465,6 +501,8 @@ void PacketCache::acknowledgeReception(std::span