r-type  0.0.0
R-Type main
Loading...
Searching...
No Matches
asioServer.cpp
Go to the documentation of this file.
1///
2/// @file AsioServer.cpp
3/// @brief Asio-based implementation of INetworkServer interface
4/// @namespace srv
5///
6
9#include "Utils/Event.hpp"
10#include "Utils/EventBus.hpp"
11#include "Utils/Logger.hpp"
12#include "Utils/Security.hpp"
13
14#include <algorithm>
15#include <chrono>
16#include <random>
17#include <ranges>
18
19namespace srv
20{
21
23 : m_ioContext(std::make_unique<asio::io_context>()),
24 m_socket(std::make_unique<asio::ip::udp::socket>(*m_ioContext)), m_port(0), m_tickRate(60), m_serverCaps(0),
25 m_running(false), m_started(false), m_nextSessionId(1), m_nextLobbyId(1),
26 m_packetHandler(std::make_unique<rnp::HandlerPacket>()), m_pingInterval(std::chrono::seconds(5)),
27 m_clientTimeout(std::chrono::seconds(30)), m_componentId(utl::NETWORK_SERVER),
28 m_eventBus(utl::EventBus::getInstance())
29 {
30 // utl::Logger::log("AsioServer: Constructor called", utl::LogLevel::INFO);
31 // utl::Logger::log("AsioServer: Creating I/O context and socket", utl::LogLevel::INFO);
33 // utl::Logger::log("AsioServer: Packet handlers setup complete", utl::LogLevel::INFO);
34
35 // EventBus integration
41
42 // utl::Logger::log("AsioServer: Initialized with EventBus integration", utl::LogLevel::INFO);
43 }
44
46 {
47 // utl::Logger::log("AsioServer: Destructor called", utl::LogLevel::INFO);
49 // utl::Logger::log("AsioServer: Destroyed", utl::LogLevel::INFO);
50 }
51
52 void AsioServer::init(const std::string &host, std::uint16_t port)
53 {
54 // utl::Logger::log("AsioServer: init() called with host=" + host + ", port=" + std::to_string(port),
55 // utl::LogLevel::INFO);
56 m_host = host;
57 m_port = port;
58 // utl::Logger::log("AsioServer: Configuration stored - ready to start", utl::LogLevel::INFO);
59 }
60
62 {
63 // utl::Logger::log("AsioServer: Starting server...", utl::LogLevel::INFO);
64
65 if (m_started.load())
66 {
67 // utl::Logger::log("AsioServer: Already started", utl::LogLevel::WARNING);
68 return;
69 }
70
71 try
72 {
73 // utl::Logger::log("AsioServer: Creating endpoint for " + m_host + ":" + std::to_string(m_port),
74 // utl::LogLevel::INFO);
75
76 // Create endpoint
77 asio::ip::udp::endpoint endpoint(asio::ip::make_address(m_host), m_port);
78 // utl::Logger::log("AsioServer: Endpoint created successfully", utl::LogLevel::INFO);
79
80 // Bind socket
81 // utl::Logger::log("AsioServer: Opening socket...", utl::LogLevel::INFO);
82 m_socket->open(endpoint.protocol());
83 // utl::Logger::log("AsioServer: Setting socket options...", utl::LogLevel::INFO);
84 m_socket->set_option(asio::ip::udp::socket::reuse_address(true));
85 // utl::Logger::log("AsioServer: Binding socket to endpoint...", utl::LogLevel::INFO);
86 m_socket->bind(endpoint);
87 // utl::Logger::log("AsioServer: Socket bound successfully", utl::LogLevel::INFO);
88
89 m_running.store(true);
90 m_started.store(true);
91
92 // Start network thread
93 // utl::Logger::log("AsioServer: Starting network thread...", utl::LogLevel::INFO);
94 m_networkThread = std::make_unique<std::thread>(&AsioServer::networkThreadLoop, this);
95
96 // Start receiving
97 // utl::Logger::log("AsioServer: Starting packet reception...", utl::LogLevel::INFO);
99
100 // utl::Logger::log("AsioServer: Started on " + m_host + ":" + std::to_string(m_port), utl::LogLevel::INFO);
101 }
102 catch (const std::exception &e)
103 {
104 m_running.store(false);
105 m_started.store(false);
106 // utl::Logger::log("AsioServer: Failed to start - " + std::string(e.what()), utl::LogLevel::WARNING);
107 throw;
108 }
109 }
110
112 {
113 if (!m_started.load())
114 {
115 return;
116 }
117
118 m_running.store(false);
119 m_started.store(false);
120
121 // Send disconnect to all clients
122 {
123 std::lock_guard<std::mutex> lock(m_clientsMutex);
124 for (auto &[sessionId, client] : m_clients)
125 {
126 if (client.isConnected)
127 {
128 rnp::Serializer serializer;
129 rnp::PacketHeader header{};
130 header.type = static_cast<std::uint8_t>(rnp::PacketType::DISCONNECT);
131 header.length = sizeof(rnp::PacketDisconnect);
132 header.sessionId = sessionId;
133
134 rnp::PacketDisconnect disconnect{};
135 disconnect.reasonCode = static_cast<std::uint16_t>(rnp::DisconnectReason::SERVER_SHUTDOWN);
136
137 serializer.serializeHeader(header);
138 serializer.serializeDisconnect(disconnect);
139
140 sendPacketImmediate(serializer.getData(), client.endpoint);
141 }
142 }
143 }
144
145 // Stop io_context safely
146 if (m_ioContext)
147 {
148 m_ioContext->stop();
149 }
150
151 // Wait for network thread with timeout
152 if (m_networkThread && m_networkThread->joinable())
153 {
154 auto joinStart = std::chrono::steady_clock::now();
155 bool joined = false;
156
157 while (
158 !joined &&
159 std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - joinStart).count() <
160 5)
161 {
162 if (m_networkThread->joinable())
163 {
164 m_networkThread->join();
165 joined = true;
166 }
167 else
168 {
169 std::this_thread::sleep_for(std::chrono::milliseconds(10));
170 }
171 }
172
173 if (!joined)
174 {
175 // utl::Logger::log("AsioServer: Network thread join timeout, detaching", utl::LogLevel::WARNING);
176 m_networkThread->detach();
177 }
178 }
179
180 // Close socket safely
181 {
182 std::lock_guard<std::mutex> socketLock(m_socketMutex);
183 if (m_socket && m_socket->is_open())
184 {
185 try
186 {
187 m_socket->close();
188 }
189 catch (const std::exception &e)
190 {
191 // utl::Logger::log("AsioServer: Exception closing socket - " + std::string(e.what()),
192 // utl::LogLevel::WARNING);
193 }
194 }
195 }
196
197 // Clear clients
198 {
199 std::lock_guard<std::mutex> lock(m_clientsMutex);
200 m_clients.clear();
201 m_endpointToSession.clear();
202 }
203
204 // utl::Logger::log("AsioServer: Stopped", utl::LogLevel::INFO);
205 }
206
208 {
210
211 if (!m_running.load())
212 {
213 return;
214 }
215
216 // Process send queue
218
219 // Update client management (timeouts, pings)
221 }
222
223 void AsioServer::sendToClient(std::uint32_t sessionId, const std::vector<std::uint8_t> &data, bool reliable)
224 {
225 std::lock_guard<std::mutex> clientLock(m_clientsMutex);
226 auto it = m_clients.find(sessionId);
227 if (it == m_clients.end() || !it->second.isConnected)
228 {
229 // utl::Logger::log("AsioServer: Attempted to send to invalid session " + std::to_string(sessionId),
230 // utl::LogLevel::WARNING);
231 return;
232 }
233
234 std::lock_guard<std::mutex> queueLock(m_sendQueueMutex);
235 m_sendQueue.emplace(data, it->second.endpoint, reliable);
236 }
237
238 void AsioServer::sendToAllClients(const std::vector<std::uint8_t> &data, bool reliable)
239 {
240 std::lock_guard<std::mutex> clientLock(m_clientsMutex);
241 std::lock_guard<std::mutex> queueLock(m_sendQueueMutex);
242
243 for (const auto &client : m_clients | std::views::values)
244 {
245 if (client.isConnected)
246 {
247 m_sendQueue.emplace(data, client.endpoint, reliable);
248 }
249 }
250 }
251
252 void AsioServer::disconnectClient(std::uint32_t sessionId)
253 {
254 std::uint32_t lobbyId = 0;
255
256 // Scope for clientsLock to release it before broadcastLobbyUpdate
257 {
258 std::lock_guard<std::mutex> lock(m_clientsMutex);
259 auto it = m_clients.find(sessionId);
260 if (it == m_clients.end())
261 {
262 return;
263 }
264
265 utl::Logger::log("AsioServer: Disconnecting client " + std::to_string(sessionId), utl::LogLevel::INFO);
266
267 rnp::PacketHeader header;
268 header.type = static_cast<std::uint8_t>(rnp::PacketType::DISCONNECT);
269 header.length = static_cast<std::uint16_t>(sizeof(rnp::PacketDisconnect));
270 header.sessionId = sessionId;
271
272 std::vector<std::uint8_t> packet;
273 rnp::Serializer serializer(packet);
274 rnp::PacketDisconnect disconnect{};
275 disconnect.reasonCode = static_cast<std::uint16_t>(rnp::DisconnectReason::CLIENT_REQUEST);
276
277 serializer.serializeHeader(header);
278 serializer.serializeDisconnect(disconnect);
279
280 sendPacketImmediate(serializer.getData(), it->second.endpoint);
281
282 // Remove from lobby if in one
283 if (it->second.currentLobbyId != 0)
284 {
285 lobbyId = it->second.currentLobbyId;
286 }
287
288 // Remove from endpoint mapping
289 const std::string endpointStr = endpointToString(it->second.endpoint);
290 m_endpointToSession.erase(endpointStr);
291
292 // Remove client
293 m_clients.erase(it);
294
295 m_packetHandler->clearSession(sessionId);
296
297 // utl::Logger::log("AsioServer: Disconnected client " + std::to_string(sessionId), utl::LogLevel::INFO);
298 } // clientsLock released here
299
300 // Leave lobby AFTER releasing clientsLock to avoid nested mutex acquisitions
301 if (lobbyId != 0)
302 {
303 leaveLobby(sessionId);
304 // Broadcast lobby update to remaining players
305 broadcastLobbyUpdate(lobbyId);
306 }
307 }
308
309 std::size_t AsioServer::getClientCount() const
310 {
311 std::lock_guard clientLock(m_clientsMutex);
312 return std::ranges::count_if(m_clients, [](const auto &pair) { return pair.second.isConnected; });
313 }
314
315 std::vector<std::uint32_t> AsioServer::getConnectedSessions() const
316 {
317 std::lock_guard clientLock(m_clientsMutex);
318 std::vector<std::uint32_t> sessions;
319 for (const auto &[sessionId, client] : m_clients)
320 {
321 if (client.isConnected)
322 {
323 sessions.push_back(sessionId);
324 }
325 }
326 return sessions;
327 }
328
329 bool AsioServer::isRunning() const { return m_running.load(); }
330
331 void AsioServer::setTickRate(std::uint16_t tickRate)
332 {
333 m_tickRate = tickRate;
334 // utl::Logger::log("AsioServer: Tick rate set to " + std::to_string(tickRate), utl::LogLevel::INFO);
335 }
336
337 void AsioServer::setServerCapabilities(std::uint32_t caps)
338 {
339 m_serverCaps = caps;
340 // utl::Logger::log("AsioServer: Server capabilities set to " + std::to_string(caps), utl::LogLevel::INFO);
341 }
342
344 {
345 // CONNECT handler
346 m_packetHandler->onConnect([this](const rnp::PacketConnect &packet, const rnp::PacketContext &context)
347 { return handleConnect(packet, context); });
348
349 // DISCONNECT handler
350 m_packetHandler->onDisconnect([this](const rnp::PacketDisconnect &packet, const rnp::PacketContext &context)
351 { return handleDisconnect(packet, context); });
352
353 // PING handler
354 m_packetHandler->onPing([this](const rnp::PacketPingPong &packet, const rnp::PacketContext &context)
355 { return handlePing(packet, context); });
356
357 // PONG handler
358 m_packetHandler->onPong([this](const rnp::PacketPingPong &packet, const rnp::PacketContext &context)
359 { return handlePong(packet, context); });
360
361 // ENTITY_EVENT handler (for player inputs)
363 [this](const std::vector<rnp::EventRecord> &events, const rnp::PacketContext &context)
364 { return handleEntityEvent(events, context); });
365
366 // LOBBY_LIST_REQUEST handler
368 { return handleLobbyListRequest(context); });
369
370 // LOBBY_CREATE handler
371 m_packetHandler->onLobbyCreate([this](const rnp::PacketLobbyCreate &packet, const rnp::PacketContext &context)
372 { return handleLobbyCreate(packet, context); });
373
374 // LOBBY_JOIN handler
375 m_packetHandler->onLobbyJoin([this](const rnp::PacketLobbyJoin &packet, const rnp::PacketContext &context)
376 { return handleLobbyJoin(packet, context); });
377
378 // LOBBY_LEAVE handler
379 m_packetHandler->onLobbyLeave([this](const rnp::PacketContext &context) { return handleLobbyLeave(context); });
380
381 // START_GAME_REQUEST handler
383 [this](const rnp::PacketStartGameRequest &packet, const rnp::PacketContext &context)
384 { return handleStartGameRequest(packet, context); });
385
386 // utl::Logger::log("AsioServer: Packet handlers initialized", utl::LogLevel::INFO);
387 }
388
390 {
391 // Check if we should continue receiving
392 if (!m_running.load() || !m_socket || !m_socket->is_open())
393 {
394 utl::Logger::log("AsioServer: Cannot start receive - server not running or socket closed",
396 return;
397 }
398
399 m_socket->async_receive_from(
400 asio::buffer(m_recvBuffer), m_senderEndpoint,
401 [this](const std::error_code ec, const std::size_t bytesReceived)
402 {
403 if (!ec && m_running.load())
404 {
405 utl::Logger::log("AsioServer: Received " + std::to_string(bytesReceived) + " bytes from " +
406 m_senderEndpoint.address().to_string() + ":" +
407 std::to_string(m_senderEndpoint.port()),
408 utl::LogLevel::INFO);
409 handleReceive(bytesReceived);
410 startReceive(); // Continue receiving
411 }
412 else if (m_running.load() && ec != asio::error::operation_aborted)
413 {
414 utl::Logger::log("AsioServer: Receive error - " + ec.message(), utl::LogLevel::WARNING);
415 std::this_thread::sleep_for(std::chrono::milliseconds(100));
416 startReceive(); // Try to continue
417 }
418 else
419 {
420 utl::Logger::log("AsioServer: Server stopped or operation aborted, ending receive loop",
421 utl::LogLevel::INFO);
422 }
423 });
424 }
425
426 void AsioServer::handleReceive(std::size_t bytesReceived)
427 {
428 utl::Logger::log("AsioServer: Processing received packet of " + std::to_string(bytesReceived) + " bytes",
430
431 // PacketHeader serialized size: 1 (type) + 2 (length) + 4 (sessionId) = 7 bytes
432 constexpr std::size_t PACKET_HEADER_SIZE = 7;
433 if (bytesReceived < PACKET_HEADER_SIZE)
434 {
435 utl::Logger::log("AsioServer: Received packet too small (need " + std::to_string(PACKET_HEADER_SIZE) +
436 " bytes, got " + std::to_string(bytesReceived) + ")",
438 return;
439 }
440
441 // Create packet context
442 rnp::PacketContext context;
443 context.receiveTime = std::chrono::steady_clock::now();
444 context.senderAddress = m_senderEndpoint.address().to_string();
445 context.senderPort = m_senderEndpoint.port();
446
447 utl::Logger::log("AsioServer: Packet from " + context.senderAddress + ":" + std::to_string(context.senderPort),
449
450 // Extract session ID from header for context
451 std::vector<std::uint8_t> data(m_recvBuffer.begin(), m_recvBuffer.begin() + bytesReceived);
452 if (data.size() >= PACKET_HEADER_SIZE)
453 {
454 rnp::Serializer headerSerializer(data);
455 rnp::PacketHeader header = headerSerializer.deserializeHeader();
456 context.sessionId = header.sessionId;
457 utl::Logger::log("AsioServer: Packet type: " + std::to_string(static_cast<int>(header.type)) +
458 ", Session ID: " + std::to_string(header.sessionId),
460 }
461
462 // Process packet
463 utl::Logger::log("AsioServer: Processing packet...", utl::LogLevel::INFO);
464 rnp::HandlerResult result = m_packetHandler->processPacket(data, context);
465 if (result != rnp::HandlerResult::SUCCESS)
466 {
467 utl::Logger::log("AsioServer: Packet processing failed with result " +
468 std::to_string(static_cast<int>(result)),
470 }
471 else
472 {
473 utl::Logger::log("AsioServer: Packet processed successfully", utl::LogLevel::INFO);
474 }
475 }
476
478 {
479 // utl::Logger::log("AsioServer: Network thread started", utl::LogLevel::INFO);
480
481 while (m_running.load())
482 {
483 try
484 {
485 // Run I/O context and check if we should continue
486 if (!m_running.load())
487 {
488 break;
489 }
490
491 size_t handlersRun = m_ioContext->run_one();
492 if (handlersRun == 0)
493 {
494 // No handlers to run, sleep briefly to avoid busy waiting
495 std::this_thread::sleep_for(std::chrono::milliseconds(1));
496 }
497 }
498 catch (const std::exception &e)
499 {
500 if (m_running.load())
501 {
502 // utl::Logger::log("AsioServer: Network thread exception - " + std::string(e.what()),
503 // utl::LogLevel::WARNING);
504 std::this_thread::sleep_for(std::chrono::milliseconds(100));
505 }
506 }
507 }
508
509 // utl::Logger::log("AsioServer: Network thread stopped", utl::LogLevel::INFO);
510 }
511
512 std::uint32_t AsioServer::generateSessionId() const
513 {
514 std::random_device rd;
515 std::mt19937 gen(rd());
516 std::uniform_int_distribution<std::uint32_t> dis(1, UINT32_MAX);
517
518 std::uint32_t sessionId = 0;
519 while (sessionId == 0 || m_clients.contains(sessionId))
520 {
521 sessionId = dis(gen);
522 }
523
524 return sessionId;
525 }
526
527 std::string AsioServer::endpointToString(const asio::ip::udp::endpoint &endpoint)
528 {
529 return endpoint.address().to_string() + ":" + std::to_string(endpoint.port());
530 }
531
532 void AsioServer::sendPacketImmediate(const std::vector<std::uint8_t> &data,
533 const asio::ip::udp::endpoint &destination)
534 {
535 utl::Logger::log("AsioServer: Sending " + std::to_string(data.size()) + " bytes to " +
536 destination.address().to_string() + ":" + std::to_string(destination.port()),
538
539 // Protect socket access with mutex
540 std::lock_guard<std::mutex> socketLock(m_socketMutex);
541
542 if (!m_socket || !m_socket->is_open() || !m_running.load())
543 {
544 utl::Logger::log("AsioServer: Cannot send - socket not open or server not running", utl::LogLevel::WARNING);
545 return;
546 }
547
548 try
549 {
550 size_t bytesSent = m_socket->send_to(asio::buffer(data), destination);
551 utl::Logger::log("AsioServer: Successfully sent " + std::to_string(bytesSent) + " bytes",
553 }
554 catch (const std::exception &e)
555 {
556 if (m_running.load())
557 {
558 utl::Logger::log("AsioServer: Failed to send packet - " + std::string(e.what()),
560 }
561 }
562 }
563
565 {
566 // utl::Logger::log("AsioServer: Handling CONNECT packet from " + context.senderAddress + ":" +
567 // std::to_string(context.senderPort),
568 // utl::LogLevel::INFO);
569
570 std::lock_guard<std::mutex> lock(m_clientsMutex);
571 // Check if server is full (avoid getClientCount() to prevent deadlock)
572 const size_t currentClientCount =
573 std::ranges::count_if(m_clients, [](const auto &pair) { return pair.second.isConnected; });
574 // utl::Logger::log("AsioServer: Current client count: " + std::to_string(currentClientCount) + "/" +
575 // std::to_string(MAX_CLIENTS),
576 // utl::LogLevel::INFO);
577
578 if (currentClientCount >= MAX_CLIENTS)
579 {
580 // utl::Logger::log("AsioServer: Server full, rejecting connection", utl::LogLevel::WARNING);
583 }
584
585 // Check if client already exists
586 const std::string endpointStr = endpointToString(m_senderEndpoint);
587 const auto existingIt = m_endpointToSession.find(endpointStr);
588 if (existingIt != m_endpointToSession.end())
589 {
590 // utl::Logger::log("AsioServer: Client already connected, resending accept (Session ID: " +
591 // std::to_string(existingIt->second) + ")",
592 // utl::LogLevel::INFO);
593 // Client already connected, resend accept
594 sendConnectAccept(existingIt->second, m_senderEndpoint);
596 }
597
598 // Create new session
599 std::uint32_t sessionId = generateSessionId();
600 // utl::Logger::log("AsioServer: Creating new session with ID: " + std::to_string(sessionId),
601 // utl::LogLevel::INFO);
602
603 ClientSession &client = m_clients[sessionId];
604 client.sessionId = sessionId;
605 client.endpoint = m_senderEndpoint;
606 client.lastSeen = context.receiveTime;
607
608 // Security: Validate player name format and length
609 std::string rawPlayerName(packet.playerName.data(), packet.nameLen);
610 if (!utl::InputValidator::isValidPlayerName(rawPlayerName))
611 {
612 // Invalid player name - reject connection
613 utl::Logger::log("AsioServer: Invalid player name format from " + endpointStr, utl::LogLevel::WARNING);
615 m_clients.erase(sessionId);
616 m_endpointToSession.erase(endpointStr);
618 }
619
620 client.playerName = rawPlayerName;
621 client.clientCaps = packet.clientCaps;
622 client.isConnected = true;
623
624 m_endpointToSession[endpointStr] = sessionId;
625
626 // utl::Logger::log("AsioServer: Player name: '" + client.playerName +
627 // "', Caps: " + std::to_string(client.clientCaps),
628 // utl::LogLevel::INFO);
629
630 // Send accept response
631 // utl::Logger::log("AsioServer: Sending CONNECT_ACCEPT to session " + std::to_string(sessionId),
632 // utl::LogLevel::INFO);
634
635 // utl::Logger::log("AsioServer: Client connected - " + client.playerName + " (ID: " + std::to_string(sessionId)
636 // +
637 // ")",
638 // utl::LogLevel::INFO);
639
640 // Publier vers GameServer
642 utl::GAME_LOGIC); // GameServer ID
643
644 // utl::Logger::log(
645 // "AsioServer: Nouvelle connexion publiée vers GameServer (sessionId: " + std::to_string(sessionId) + ")",
646 // utl::LogLevel::INFO);
647
649 }
650
652 const rnp::PacketContext &context)
653 {
654 // utl::Logger::log("AsioServer: Handling DISCONNECT packet from session " + std::to_string(context.sessionId) +
655 // " from " + context.senderAddress + ":" + std::to_string(context.senderPort),
656 // utl::LogLevel::INFO);
657
658 std::lock_guard<std::mutex> lock(m_clientsMutex);
659
660 auto it = m_clients.find(context.sessionId);
661 if (it != m_clients.end())
662 {
663 // utl::Logger::log("AsioServer: Client disconnected - " + it->second.playerName +
664 // " (ID: " + std::to_string(context.sessionId) + ")" +
665 // ", Reason: " + std::to_string(static_cast<int>(packet.reasonCode)),
666 // utl::LogLevel::INFO);
667
668 // Remove from endpoint mapping
669 const std::string endpointStr = endpointToString(it->second.endpoint);
670 // utl::Logger::log("AsioServer: Removing endpoint mapping: " + endpointStr, utl::LogLevel::INFO);
671 m_endpointToSession.erase(endpointStr);
672
673 // Remove client
674 // utl::Logger::log("AsioServer: Removing client from session list", utl::LogLevel::INFO);
675 m_clients.erase(it);
676 }
677 else
678 {
679 // utl::Logger::log("AsioServer: DISCONNECT from unknown session " + std::to_string(context.sessionId),
680 // utl::LogLevel::WARNING);
681 }
682
683 // Publier vers GameServer
685 utl::GAME_LOGIC); // GameServer ID
686
687 // utl::Logger::log(
688 // "AsioServer: Déconnexion publiée vers GameServer (sessionId: " + std::to_string(context.sessionId) + ")",
689 // utl::LogLevel::INFO);
690
692 }
693
695 {
696 // Update client last seen time
697 {
698 std::lock_guard<std::mutex> lock(m_clientsMutex);
699 const auto it = m_clients.find(context.sessionId);
700 if (it != m_clients.end())
701 {
702 it->second.lastSeen = context.receiveTime;
703 }
704 }
705
706 // Send pong response
707 sendPong(packet.nonce, m_senderEndpoint, context.sessionId);
709 }
710
712 {
713 // Update client latency and last seen time
714 {
715 std::lock_guard<std::mutex> lock(m_clientsMutex);
716 auto it = m_clients.find(context.sessionId);
717 if (it != m_clients.end())
718 {
719 it->second.lastSeen = context.receiveTime;
720 // Calculate latency based on ping time
721 auto now = std::chrono::steady_clock::now();
722 auto pingTime = std::chrono::milliseconds(packet.sendTimeMs);
723 auto currentTime = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch());
724 it->second.latency = static_cast<std::uint32_t>((currentTime - pingTime).count());
725 }
726 }
727
729 }
730
731 void AsioServer::sendPong(std::uint32_t nonce, const asio::ip::udp::endpoint &destination, std::uint32_t sessionId)
732 {
733 rnp::Serializer serializer;
734 rnp::PacketHeader header{};
735 header.type = static_cast<std::uint8_t>(rnp::PacketType::PONG);
736 header.length = sizeof(rnp::PacketPingPong);
737 header.sessionId = sessionId;
738
739 rnp::PacketPingPong pong{};
740 pong.nonce = nonce;
741 pong.sendTimeMs = static_cast<std::uint32_t>(
742 std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch())
743 .count());
744
745 serializer.serializeHeader(header);
746 serializer.serializePingPong(pong);
747
748 sendPacketImmediate(serializer.getData(), destination);
749 }
750
751 void AsioServer::sendConnectAccept(std::uint32_t sessionId, const asio::ip::udp::endpoint &destination)
752 {
753 // utl::Logger::log("AsioServer: Preparing CONNECT_ACCEPT packet for session " + std::to_string(sessionId) +
754 // " to " + destination.address().to_string() + ":" + std::to_string(destination.port()),
755 // utl::LogLevel::INFO);
756
757 rnp::Serializer serializer;
758 rnp::PacketHeader header{};
759 header.type = static_cast<std::uint8_t>(rnp::PacketType::CONNECT_ACCEPT);
760 header.length = sizeof(rnp::PacketConnectAccept);
761 header.sessionId = sessionId;
762
764 accept.sessionId = sessionId;
765 accept.tickRateHz = m_tickRate;
766 accept.mtuPayloadBytes = rnp::MAX_PAYLOAD;
767 accept.serverCaps = m_serverCaps;
768
769 serializer.serializeHeader(header);
770 serializer.serializeConnectAccept(accept);
771
772 // utl::Logger::log("AsioServer: Sending CONNECT_ACCEPT packet (" + std::to_string(serializer.getData().size())
773 // +
774 // " bytes)",
775 // utl::LogLevel::INFO);
776 sendPacketImmediate(serializer.getData(), destination);
777 }
778
779 void AsioServer::sendError(rnp::ErrorCode errorCode, const std::string &description,
780 const asio::ip::udp::endpoint &destination, std::uint32_t sessionId)
781 {
782 rnp::Serializer serializer;
783 rnp::PacketHeader header{};
784 header.type = static_cast<std::uint8_t>(rnp::PacketType::PACKET_ERROR);
785 header.length = static_cast<std::uint16_t>(sizeof(rnp::PacketError) + description.length());
786 header.sessionId = sessionId;
787
788 rnp::PacketError error{};
789 error.errorCode = static_cast<std::uint16_t>(errorCode);
790 error.msgLen = static_cast<std::uint16_t>(description.length());
791 error.description = description;
792
793 serializer.serializeHeader(header);
794 serializer.serializeError(error);
795
796 sendPacketImmediate(serializer.getData(), destination);
797 }
798
800 {
801 const auto now = std::chrono::steady_clock::now();
802
803 // Send pings periodically
804 if (now - m_lastPingTime > m_pingInterval)
805 {
806 std::lock_guard<std::mutex> lock(m_clientsMutex);
807 for (auto &[sessionId, client] : m_clients)
808 {
809 if (client.isConnected)
810 {
811 // Send ping
812 rnp::Serializer serializer;
813 rnp::PacketHeader header{};
814 header.type = static_cast<std::uint8_t>(rnp::PacketType::PING);
815 header.length = sizeof(rnp::PacketPingPong);
816 header.sessionId = sessionId;
817
818 rnp::PacketPingPong ping{};
819 ping.nonce = ++client.lastPingSent;
820 ping.sendTimeMs = static_cast<std::uint32_t>(
821 std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count());
822
823 serializer.serializeHeader(header);
824 serializer.serializePingPong(ping);
825
826 std::lock_guard<std::mutex> queueLock(m_sendQueueMutex);
827 m_sendQueue.emplace(serializer.getData(), client.endpoint, false);
828 }
829 }
830 m_lastPingTime = now;
831 }
832
833 // Check for timeouts
834 std::vector<std::uint32_t> timedOutClients;
835 {
836 std::lock_guard<std::mutex> lock(m_clientsMutex);
837 for (const auto &[sessionId, client] : m_clients)
838 {
839 if (client.isConnected && (now - client.lastSeen) > m_clientTimeout)
840 {
841 timedOutClients.push_back(sessionId);
842 }
843 }
844 }
845
846 // Disconnect timed out clients
847 for (std::uint32_t sessionId : timedOutClients)
848 {
849 // utl::Logger::log("AsioServer: Client " + std::to_string(sessionId) + " timed out", utl::LogLevel::INFO);
850 disconnectClient(sessionId);
851 }
852 }
853
855 {
856 std::lock_guard<std::mutex> lock(m_sendQueueMutex);
857 while (!m_sendQueue.empty())
858 {
859 const QueuedPacket &packet = m_sendQueue.front();
860 sendPacketImmediate(packet.data, packet.destination);
861 m_sendQueue.pop();
862 }
863 }
864
866 {
867 // Consommer les événements du GameServer
868 const auto events = m_eventBus.consumeForTarget(m_componentId);
869
870 for (const auto &event : events)
871 {
872 switch (event.type)
873 {
876 break;
878 handleGameOverEvent(event);
879 break;
880
883 break;
884
887 break;
888
889 default:
890 // utl::Logger::log("AsioServer: Événement non géré: " +
891 // std::to_string(static_cast<uint32_t>(event.type)),
892 // utl::LogLevel::WARNING);
893 break;
894 }
895 }
896 }
897
899 {
900 // Extraire l'ID du client cible et les données
901 if (event.data.size() >= sizeof(uint32_t))
902 {
903 uint32_t sessionId = 0;
904 std::memcpy(&sessionId, event.data.data(), sizeof(uint32_t));
905
906 std::vector<uint8_t> messageData(event.data.begin() + sizeof(uint32_t), event.data.end());
907
908 // Utiliser la méthode existante
909 sendToClient(sessionId, messageData);
910
911 // utl::Logger::log("AsioServer: Message envoye au client " + std::to_string(sessionId) +
912 // " (taille: " + std::to_string(messageData.size()) + " bytes)",
913 // utl::LogLevel::INFO);
914 }
915 }
916
918 {
919 // Utiliser la méthode existante pour broadcaster
920 sendToAllClients(event.data);
921
922 // utl::Logger::log(
923 // "AsioServer: Message diffuse a tous les clients (taille: " + std::to_string(event.data.size()) + "
924 // bytes)", utl::LogLevel::INFO);
925 }
926
928 {
929 // Forward entity events to all clients
930 sendToAllClients(event.data);
931
932 // utl::Logger::log("AsioServer: Entity events diffuses a tous les clients (taille: " +
933 // std::to_string(event.data.size()) + " bytes)",
934 // utl::LogLevel::INFO);
935 }
936
937 rnp::HandlerResult AsioServer::handleEntityEvent(const std::vector<rnp::EventRecord> &events,
938 const rnp::PacketContext &context) const
939 {
940 utl::Logger::log("AsioServer: Received " + std::to_string(events.size()) + " entity events from session " +
941 std::to_string(context.sessionId),
943
944 // Filtrer les événements d'input et les publier vers GameServer
945 for (const auto &eventRecord : events)
946 {
947 if (eventRecord.type == rnp::EventType::INPUT)
948 {
949 utl::Logger::log("AsioServer: Input event received, data size: " +
950 std::to_string(eventRecord.data.size()),
952
953 // Create event with sessionId as sourceId so RTypeServer knows which player
955 event.data = eventRecord.data;
956 m_eventBus.publish(event);
957
958 // utl::Logger::log("AsioServer: Input joueur publie vers GameServer (sessionId: " +
959 // std::to_string(context.sessionId) + ")",
960 // utl::LogLevel::INFO);
961 }
962 else
963 {
965 utl::GAME_LOGIC); // GameServer ID
966
967 // utl::Logger::log("AsioServer: Evenement entite publie vers GameServer (type: " +
968 // std::to_string(static_cast<uint8_t>(eventRecord.type)) + ")",
969 // utl::LogLevel::INFO);
970 }
971 }
972
974 }
975
976 // Lobby System Implementation
977
979 {
980 utl::Logger::log("AsioServer: Handling LOBBY_LIST_REQUEST from session " + std::to_string(context.sessionId),
982
983 // Verify session exists
984 {
985 std::lock_guard<std::mutex> clientsLock(m_clientsMutex);
986 auto clientIt = m_clients.find(context.sessionId);
987 if (clientIt == m_clients.end() || !clientIt->second.isConnected)
988 {
991 }
992 } // Release the lock before calling sendLobbyList
993
994 sendLobbyList(context.sessionId);
996 }
997
999 const rnp::PacketContext &context)
1000 {
1001 utl::Logger::log("AsioServer: Handling LOBBY_CREATE from session " + std::to_string(context.sessionId),
1003
1004 std::uint32_t lobbyId = 0;
1005 bool success = false;
1006
1007 // Scope for clientsLock to release it before broadcastLobbyUpdate
1008 {
1009 std::lock_guard<std::mutex> clientsLock(m_clientsMutex);
1010 auto clientIt = m_clients.find(context.sessionId);
1011 if (clientIt == m_clients.end() || !clientIt->second.isConnected)
1012 {
1015 }
1016
1017 utl::Logger::log("AsioServer: LOBBY_JOIN - Client current lobby ID: " +
1018 std::to_string(clientIt->second.currentLobbyId),
1020
1021 if (clientIt->second.currentLobbyId != 0)
1022 {
1025 }
1026
1027 // Extract and validate lobby name
1028 std::string lobbyName;
1029 if (packet.nameLen > 0 && packet.nameLen <= 31)
1030 {
1031 lobbyName = std::string(packet.lobbyName.data(), packet.nameLen);
1032
1033 // Security: Validate lobby name format
1035 {
1036 utl::Logger::log("AsioServer: Invalid lobby name format", utl::LogLevel::WARNING);
1038 }
1039 }
1040 else
1041 {
1042 lobbyName = "Lobby " + std::to_string(m_nextLobbyId);
1043 }
1044
1045 // Create lobby
1046 lobbyId = createLobby(lobbyName, context.sessionId, packet.maxPlayers, packet.gameMode);
1047 if (lobbyId > 0)
1048 {
1049 clientIt->second.currentLobbyId = lobbyId;
1050 sendLobbyCreateResponse(context.sessionId, lobbyId, true);
1051 success = true;
1052 }
1053 else
1054 {
1056 }
1057 } // clientsLock released here
1058
1059 // Broadcast lobby update AFTER releasing clientsLock to avoid deadlock
1060 if (success && lobbyId > 0)
1061 {
1062 broadcastLobbyUpdate(lobbyId);
1063 }
1064
1066 }
1067
1069 const rnp::PacketContext &context)
1070 {
1071 utl::Logger::log("AsioServer: Handling LOBBY_JOIN from session " + std::to_string(context.sessionId) +
1072 " for lobby " + std::to_string(packet.lobbyId),
1074
1075 bool joinSuccess = false;
1076
1077 // Scope for clientsLock to release it before lobbyToLobbyInfo and broadcastLobbyUpdate
1078 {
1079 std::lock_guard<std::mutex> clientsLock(m_clientsMutex);
1080 utl::Logger::log("AsioServer: LOBBY_JOIN - Checking client session " + std::to_string(context.sessionId),
1082 auto clientIt = m_clients.find(context.sessionId);
1083 if (clientIt == m_clients.end() || !clientIt->second.isConnected)
1084 {
1085 utl::Logger::log("AsioServer: LOBBY_JOIN failed - unauthorized session " +
1086 std::to_string(context.sessionId),
1090 }
1091
1092 if (clientIt->second.currentLobbyId != 0)
1093 {
1094 utl::Logger::log("AsioServer: LOBBY_JOIN failed - session " + std::to_string(context.sessionId) +
1095 " already in lobby " + std::to_string(clientIt->second.currentLobbyId),
1099 }
1100
1101 // Try to join lobby
1102 LobbyStatus joinStatus = joinLobby(packet.lobbyId, context.sessionId);
1103 utl::Logger::log("AsioServer: joinLobby returned status: " + std::to_string(static_cast<int>(joinStatus)),
1105
1106 if (joinStatus == LobbyStatus::SUCCESS)
1107 {
1108 clientIt->second.currentLobbyId = packet.lobbyId;
1109 utl::Logger::log("AsioServer: Session " + std::to_string(context.sessionId) +
1110 " successfully joined lobby " + std::to_string(packet.lobbyId),
1112 joinSuccess = true;
1113 }
1114 else
1115 {
1116 utl::Logger::log("AsioServer: Session " + std::to_string(context.sessionId) + " failed to join lobby " +
1117 std::to_string(packet.lobbyId) +
1118 " - status: " + std::to_string(static_cast<int>(joinStatus)),
1121 nullptr);
1122 }
1123 } // clientsLock released here
1124
1125 // Get lobby info AFTER releasing clientsLock to avoid deadlock in lobbyToLobbyInfo
1126 if (joinSuccess)
1127 {
1128 rnp::LobbyInfo lobbyInfo;
1129 {
1130 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1131 auto lobbyIt = m_lobbies.find(packet.lobbyId);
1132 if (lobbyIt != m_lobbies.end())
1133 {
1134 lobbyInfo = lobbyToLobbyInfo(lobbyIt->second);
1135 utl::Logger::log("AsioServer: Lobby info - currentPlayers: " +
1136 std::to_string(static_cast<int>(lobbyInfo.currentPlayers)) + "/" +
1137 std::to_string(static_cast<int>(lobbyInfo.maxPlayers)),
1139 }
1140 }
1141
1142 sendLobbyJoinResponse(context.sessionId, packet.lobbyId, true, rnp::ErrorCode::NONE, &lobbyInfo);
1143 }
1144
1145 // Broadcast lobby update AFTER releasing clientsLock to avoid deadlock
1146 if (joinSuccess)
1147 {
1149 }
1150
1152 }
1153
1155 {
1156 utl::Logger::log("AsioServer: Handling LOBBY_LEAVE from session " + std::to_string(context.sessionId),
1158
1159 std::uint32_t lobbyId = 0;
1160
1161 // Scope for clientsLock to release it before broadcastLobbyUpdate
1162 {
1163 std::lock_guard<std::mutex> clientsLock(m_clientsMutex);
1164 auto clientIt = m_clients.find(context.sessionId);
1165 if (clientIt == m_clients.end() || !clientIt->second.isConnected)
1166 {
1169 }
1170
1171 // Check if client is in a lobby
1172 if (clientIt->second.currentLobbyId == 0)
1173 {
1176 }
1177
1178 lobbyId = clientIt->second.currentLobbyId;
1179 clientIt->second.currentLobbyId = 0; // Clear lobby association before leaving
1180 } // clientsLock released here
1181
1182 // Leave lobby after releasing clientsLock to avoid nested mutex acquisitions
1183 leaveLobby(context.sessionId);
1184
1185 // Broadcast update to remaining players AFTER releasing clientsLock to avoid deadlock
1186 if (lobbyId != 0)
1187 {
1188 broadcastLobbyUpdate(lobbyId);
1189 }
1190
1192 }
1193
1194 void AsioServer::sendLobbyList(const std::uint32_t sessionId)
1195 {
1196 utl::Logger::log("AsioServer: Sending lobby list to session " + std::to_string(sessionId), utl::LogLevel::INFO);
1197
1199 {
1200 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1201 response.lobbyCount = static_cast<std::uint16_t>(m_lobbies.size());
1202 response.lobbies.reserve(m_lobbies.size());
1203
1204 for (const auto &lobby : m_lobbies | std::views::values)
1205 {
1206 rnp::LobbyInfo lobbyInfo = lobbyToLobbyInfo(lobby);
1207 utl::Logger::log("AsioServer: Adding lobby " + std::to_string(lobbyInfo.lobbyId) + " with " +
1208 std::to_string(static_cast<int>(lobbyInfo.currentPlayers)) + "/" +
1209 std::to_string(static_cast<int>(lobbyInfo.maxPlayers)) + " players",
1211 response.lobbies.push_back(lobbyInfo);
1212 }
1213 }
1214
1215 // Serialize header and response data
1216 std::vector<std::uint8_t> packet;
1217 rnp::Serializer packetSerializer(packet);
1218
1219 rnp::PacketHeader header;
1220 header.type = static_cast<std::uint8_t>(rnp::PacketType::LOBBY_LIST_RESPONSE);
1221 header.length = static_cast<std::uint16_t>(sizeof(rnp::PacketLobbyListResponse));
1222 header.sessionId = sessionId;
1223
1224 packetSerializer.serializeHeader(header);
1225 packetSerializer.serializeLobbyListResponse(response);
1226
1227 utl::Logger::log("AsioServer: Sending lobby list with " + std::to_string(response.lobbyCount) +
1228 " lobbies, packet size: " + std::to_string(packetSerializer.getData().size()),
1230
1231 sendPacketImmediate(packetSerializer.getData(), m_senderEndpoint);
1232 }
1233
1234 std::uint32_t AsioServer::createLobby(const std::string &name, std::uint32_t hostSession, std::uint8_t maxPlayers,
1235 std::uint8_t gameMode)
1236 {
1237 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1238
1239 std::uint32_t lobbyId = m_nextLobbyId++;
1240 Lobby lobby(lobbyId, name, hostSession, maxPlayers, gameMode);
1241 lobby.playerSessions.push_back(hostSession);
1242
1243 m_lobbies[lobbyId] = std::move(lobby);
1244
1245 utl::Logger::log("AsioServer: Created lobby " + std::to_string(lobbyId) + " '" + name + "' hosted by " +
1246 std::to_string(hostSession),
1248
1249 return lobbyId;
1250 }
1251
1252 LobbyStatus AsioServer::joinLobby(std::uint32_t lobbyId, std::uint32_t sessionId)
1253 {
1254 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1255
1256 utl::Logger::log("AsioServer: joinLobby - Looking for lobby " + std::to_string(lobbyId), utl::LogLevel::INFO);
1257
1258 auto lobbyIt = m_lobbies.find(lobbyId);
1259 if (lobbyIt == m_lobbies.end())
1260 {
1261 utl::Logger::log("AsioServer: joinLobby - Lobby " + std::to_string(lobbyId) + " not found",
1264 }
1265
1266 Lobby &lobby = lobbyIt->second;
1268 "AsioServer: joinLobby - Lobby found, current players: " + std::to_string(lobby.playerSessions.size()) +
1269 "/" + std::to_string(lobby.maxPlayers) + ", status: " + std::to_string(static_cast<int>(lobby.status)),
1271
1272 if (lobby.playerSessions.size() >= lobby.maxPlayers)
1273 {
1274 utl::Logger::log("AsioServer: joinLobby - Lobby is full", utl::LogLevel::WARNING);
1275 return LobbyStatus::FULL;
1276 }
1277
1278 if (lobby.status != rnp::LobbyStatus::WAITING)
1279 {
1280 utl::Logger::log("AsioServer: joinLobby - Game already started", utl::LogLevel::WARNING);
1281 return LobbyStatus::IN_GAME;
1282 }
1283
1284 if (auto playerIt = std::ranges::find(lobby.playerSessions, sessionId); playerIt != lobby.playerSessions.end())
1285 {
1287 }
1288
1289 lobby.playerSessions.push_back(sessionId);
1290
1291 utl::Logger::log("AsioServer: Player " + std::to_string(sessionId) + " joined lobby " +
1292 std::to_string(lobbyId) + " (now " + std::to_string(lobby.playerSessions.size()) + "/" +
1293 std::to_string(static_cast<int>(lobby.maxPlayers)) + " players)",
1295 return LobbyStatus::SUCCESS;
1296 }
1297
1298 void AsioServer::leaveLobby(std::uint32_t sessionId)
1299 {
1300 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1301
1302 for (auto &[lobbyId, lobby] : m_lobbies)
1303 {
1304 if (auto playerIt = std::ranges::find(lobby.playerSessions, sessionId);
1305 playerIt != lobby.playerSessions.end())
1306 {
1307 lobby.playerSessions.erase(playerIt);
1308
1309 utl::Logger::log("AsioServer: Player " + std::to_string(sessionId) + " left lobby " +
1310 std::to_string(lobbyId),
1312
1313 // Clear the client's current lobby association
1314 {
1315 std::lock_guard<std::mutex> clientsLock(m_clientsMutex);
1316 auto clientIt = m_clients.find(sessionId);
1317 if (clientIt != m_clients.end())
1318 {
1319 clientIt->second.currentLobbyId = 0;
1320 utl::Logger::log("AsioServer: Cleared lobby association for session " +
1321 std::to_string(sessionId),
1323 }
1324 }
1325
1326 // If host left, assign new host or delete lobby
1327 if (lobby.hostSessionId == sessionId)
1328 {
1329 if (!lobby.playerSessions.empty())
1330 {
1331 lobby.hostSessionId = lobby.playerSessions[0];
1332 utl::Logger::log("AsioServer: New host for lobby " + std::to_string(lobbyId) + " is " +
1333 std::to_string(lobby.hostSessionId),
1335 }
1336 else
1337 {
1338 // No players left, lobby will be cleaned up
1339 utl::Logger::log("AsioServer: Lobby " + std::to_string(lobbyId) + " is now empty",
1341 }
1342 }
1343 break;
1344 }
1345 }
1346
1348 }
1349
1351 {
1352 utl::Logger::log("AsioServer: Received GAME_OVER event - closing all active lobbies", utl::LogLevel::INFO);
1353
1354 // Broadcast GAME_OVER to all clients in lobbies
1355 std::vector<std::uint32_t> lobbyIds;
1356 {
1357 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1358 for (const auto &[lobbyId, lobby] : m_lobbies)
1359 {
1360 lobbyIds.push_back(lobbyId);
1361 }
1362 }
1363
1364 // Send GAME_OVER packet to all clients in each lobby
1365 for (std::uint32_t lobbyId : lobbyIds)
1366 {
1367 broadcastGameOverToLobby(lobbyId, event.data);
1368 }
1369
1370 // Close all lobbies after game over
1371 {
1372 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1373 std::lock_guard<std::mutex> clientsLock(m_clientsMutex);
1374
1375 for (auto &[sessionId, client] : m_clients)
1376 {
1377 if (client.currentLobbyId != 0)
1378 {
1379 utl::Logger::log("AsioServer: Clearing lobby association for session " + std::to_string(sessionId),
1381 client.currentLobbyId = 0;
1382 }
1383 }
1384
1385 utl::Logger::log("AsioServer: Clearing all lobbies (" + std::to_string(m_lobbies.size()) + " lobbies)",
1387 m_lobbies.clear();
1388 }
1389 }
1390
1391 void AsioServer::broadcastGameOverToLobby(std::uint32_t lobbyId, const std::vector<std::uint8_t> &gameOverData)
1392 {
1393 std::vector<std::uint32_t> playerSessions;
1394
1395 {
1396 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1397 auto lobbyIt = m_lobbies.find(lobbyId);
1398 if (lobbyIt == m_lobbies.end())
1399 {
1400 return;
1401 }
1402 playerSessions = lobbyIt->second.playerSessions;
1403 }
1404
1405 rnp::Serializer serializer;
1406 rnp::PacketHeader header;
1407 header.type = static_cast<std::uint8_t>(rnp::PacketType::GAME_OVER);
1408 header.length = static_cast<std::uint16_t>(gameOverData.size());
1409 header.sessionId = 0; // Broadcast to all
1410
1411 serializer.serializeHeader(header);
1412 // Append game over data to serializer
1413 std::vector<std::uint8_t> packetData = serializer.getData();
1414 packetData.insert(packetData.end(), gameOverData.begin(), gameOverData.end());
1415
1416 // Send to all players in lobby
1417 std::lock_guard<std::mutex> clientsLock(m_clientsMutex);
1418 for (std::uint32_t sessionId : playerSessions)
1419 {
1420 auto clientIt = m_clients.find(sessionId);
1421 if (clientIt != m_clients.end() && clientIt->second.isConnected)
1422 {
1423 utl::Logger::log("AsioServer: Sending GAME_OVER to session " + std::to_string(sessionId),
1425 sendPacketImmediate(packetData, clientIt->second.endpoint);
1426 }
1427 }
1428 }
1429
1430 void AsioServer::broadcastLobbyUpdate(std::uint32_t lobbyId)
1431 {
1432 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1433 auto lobbyIt = m_lobbies.find(lobbyId);
1434 if (lobbyIt == m_lobbies.end())
1435 {
1436 return;
1437 }
1438
1440 update.lobbyInfo = lobbyToLobbyInfo(lobbyIt->second);
1441
1442 // Serialize packet data
1443 std::vector<std::uint8_t> data;
1444 rnp::Serializer serializer(data);
1445 serializer.serializeLobbyUpdate(update);
1446
1447 // Get the serialized data
1448 const std::vector<std::uint8_t> &lobbyUpdateData = serializer.getData();
1449
1450 // Send to all players in lobby
1451 for (std::uint32_t sessionId : lobbyIt->second.playerSessions)
1452 {
1453 rnp::PacketHeader header;
1454 header.type = static_cast<std::uint8_t>(rnp::PacketType::LOBBY_UPDATE);
1455 header.length = static_cast<std::uint16_t>(lobbyUpdateData.size());
1456 header.sessionId = sessionId;
1457
1458 std::vector<std::uint8_t> playerPacket;
1459 rnp::Serializer playerPacketSerializer(playerPacket);
1460 playerPacketSerializer.serializeHeader(header);
1461 playerPacketSerializer.serializeLobbyUpdate(update);
1462
1463 // Lock clients mutex to safely access client data
1464 std::lock_guard<std::mutex> clientsLock(m_clientsMutex);
1465 auto clientIt = m_clients.find(sessionId);
1466 if (clientIt != m_clients.end())
1467 {
1468 sendPacketImmediate(playerPacketSerializer.getData(), clientIt->second.endpoint);
1469 }
1470 }
1471
1472 utl::Logger::log("AsioServer: Broadcasted lobby update for lobby " + std::to_string(lobbyId),
1474 }
1475
1477 {
1478 rnp::LobbyInfo info;
1479 info.lobbyId = lobby.lobbyId;
1480 info.currentPlayers = static_cast<std::uint8_t>(lobby.playerSessions.size());
1481 info.maxPlayers = lobby.maxPlayers;
1482 info.gameMode = lobby.gameMode;
1483 info.status = static_cast<std::uint8_t>(lobby.status);
1484 info.hostSessionId = lobby.hostSessionId;
1485
1486 // Copy lobby name
1487 std::size_t nameLen = std::min(lobby.lobbyName.size(), static_cast<std::size_t>(31));
1488 std::memcpy(info.lobbyName.data(), lobby.lobbyName.c_str(), nameLen);
1489 info.lobbyName[nameLen] = '\0';
1490
1491 // Copy player names
1492 std::lock_guard<std::mutex> clientsLock(m_clientsMutex);
1493 for (std::size_t i = 0; i < lobby.playerSessions.size() && i < 8; ++i)
1494 {
1495 auto clientIt = m_clients.find(lobby.playerSessions[i]);
1496 if (clientIt != m_clients.end())
1497 {
1498 std::size_t playerNameLen = std::min(clientIt->second.playerName.size(), static_cast<std::size_t>(31));
1499 std::memcpy(info.playerNames[i].data(), clientIt->second.playerName.c_str(), playerNameLen);
1500 info.playerNames[i][playerNameLen] = '\0';
1501 }
1502 }
1503
1504 return info;
1505 }
1506
1507 void AsioServer::sendLobbyCreateResponse(std::uint32_t sessionId, std::uint32_t lobbyId, bool success,
1508 rnp::ErrorCode errorCode)
1509 {
1511 response.lobbyId = lobbyId;
1512 response.success = success ? 1 : 0;
1513 response.errorCode = static_cast<std::uint16_t>(errorCode);
1514
1515 rnp::PacketHeader header;
1516 header.type = static_cast<std::uint8_t>(rnp::PacketType::LOBBY_CREATE_RESPONSE);
1517 header.length = static_cast<std::uint16_t>(sizeof(rnp::PacketLobbyCreateResponse));
1518 header.sessionId = sessionId;
1519
1520 std::vector<std::uint8_t> packet;
1521 rnp::Serializer packetSerializer(packet);
1522 packetSerializer.serializeHeader(header);
1523 packetSerializer.serializeLobbyCreateResponse(response);
1524
1525 sendPacketImmediate(packetSerializer.getData(), m_senderEndpoint);
1526
1527 utl::Logger::log("AsioServer: Sent lobby create response to session " + std::to_string(sessionId) +
1528 " (success: " + (success ? "true" : "false") + ")",
1530 }
1531
1532 void AsioServer::sendLobbyJoinResponse(std::uint32_t sessionId, std::uint32_t lobbyId, bool success,
1533 rnp::ErrorCode errorCode, const rnp::LobbyInfo *lobbyInfo)
1534 {
1536 response.lobbyId = lobbyId;
1537 response.success = success ? 1 : 0;
1538 response.errorCode = static_cast<std::uint16_t>(errorCode);
1539 if (success && lobbyInfo)
1540 {
1541 response.lobbyInfo = *lobbyInfo;
1542 }
1543
1544 rnp::PacketHeader header;
1545 header.type = static_cast<std::uint8_t>(rnp::PacketType::LOBBY_JOIN_RESPONSE);
1546 header.length = static_cast<std::uint16_t>(sizeof(rnp::PacketLobbyJoinResponse));
1547 header.sessionId = sessionId;
1548
1549 std::vector<std::uint8_t> packet;
1550 rnp::Serializer packetSerializer(packet);
1551 packetSerializer.serializeHeader(header);
1552 packetSerializer.serializeLobbyJoinResponse(response);
1553
1554 sendPacketImmediate(packetSerializer.getData(), m_senderEndpoint);
1555
1556 utl::Logger::log("AsioServer: Sent lobby join response to session " + std::to_string(sessionId) +
1557 " (success: " + (success ? "true" : "false") + ")",
1559 }
1560
1562 {
1563 // NOTE: Assumes m_lobbiesMutex is already held by caller
1564 auto it = m_lobbies.begin();
1565 while (it != m_lobbies.end())
1566 {
1567 if (it->second.playerSessions.empty())
1568 {
1569 utl::Logger::log("AsioServer: Cleaning up empty lobby " + std::to_string(it->first),
1571 it = m_lobbies.erase(it);
1572 }
1573 else
1574 {
1575 ++it;
1576 }
1577 }
1578 }
1579
1581 const rnp::PacketContext &context)
1582 {
1583 utl::Logger::log("AsioServer: Handling START_GAME_REQUEST from session " + std::to_string(context.sessionId) +
1584 " for lobby " + std::to_string(packet.lobbyId),
1586
1587 // Verify session exists and is in the lobby
1588 {
1589 std::lock_guard<std::mutex> clientsLock(m_clientsMutex);
1590 auto clientIt = m_clients.find(context.sessionId);
1591 if (clientIt == m_clients.end() || !clientIt->second.isConnected)
1592 {
1595 }
1596
1597 if (clientIt->second.currentLobbyId != packet.lobbyId)
1598 {
1599 sendError(rnp::ErrorCode::NOT_IN_LOBBY, "Not in this lobby", m_senderEndpoint, context.sessionId);
1601 }
1602 }
1603
1604 // Verify the session is the host and lobby exists
1605 {
1606 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1607 auto lobbyIt = m_lobbies.find(packet.lobbyId);
1608 if (lobbyIt == m_lobbies.end())
1609 {
1612 }
1613
1614 // Only host can start the game
1615 if (lobbyIt->second.hostSessionId != context.sessionId)
1616 {
1618 context.sessionId);
1620 }
1621
1622 // Update lobby status to IN_GAME
1623 lobbyIt->second.status = rnp::LobbyStatus::IN_GAME;
1624 }
1625
1626 utl::Logger::log("AsioServer: Starting game for lobby " + std::to_string(packet.lobbyId), utl::LogLevel::INFO);
1627
1628 // Get sessionIds of players in lobby
1629 std::vector<std::uint32_t> playerSessions;
1630 {
1631 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1632 auto lobbyIt = m_lobbies.find(packet.lobbyId);
1633 if (lobbyIt != m_lobbies.end())
1634 {
1635 playerSessions = lobbyIt->second.playerSessions;
1636 utl::Logger::log("AsioServer: Player sessions in lobby: " + std::to_string(playerSessions.size()),
1638 }
1639 }
1640
1641 // Notify RTypeServer about game start with player sessions
1643 std::vector<std::uint8_t> sessionsData;
1644 sessionsData.resize(sizeof(std::uint32_t) * playerSessions.size());
1645 for (size_t i = 0; i < playerSessions.size(); ++i)
1646 {
1647 std::memcpy(sessionsData.data() + i * sizeof(std::uint32_t), &playerSessions[i], sizeof(std::uint32_t));
1648 }
1649 startEvent.data = sessionsData;
1650 m_eventBus.publish(startEvent);
1651
1652 utl::Logger::log("AsioServer: Sent SERVER_START to RTypeServer with " + std::to_string(playerSessions.size()) +
1653 " players",
1655
1656 // Broadcast game start to all players
1658
1660 }
1661
1662 void AsioServer::broadcastGameStart(std::uint32_t lobbyId)
1663 {
1664 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1665 auto lobbyIt = m_lobbies.find(lobbyId);
1666 if (lobbyIt == m_lobbies.end())
1667 {
1668 return;
1669 }
1670
1671 rnp::PacketGameStart gameStart;
1672 gameStart.lobbyId = lobbyId;
1673
1674 // Serialize packet
1675 std::vector<std::uint8_t> data;
1676 rnp::Serializer serializer(data);
1677 serializer.serializeGameStart(gameStart);
1678
1679 const std::vector<std::uint8_t> &gameStartData = serializer.getData();
1680
1681 // Send to all players in lobby
1682 for (std::uint32_t sessionId : lobbyIt->second.playerSessions)
1683 {
1684 rnp::PacketHeader header;
1685 header.type = static_cast<std::uint8_t>(rnp::PacketType::GAME_START);
1686 header.length = static_cast<std::uint16_t>(gameStartData.size());
1687 header.sessionId = sessionId;
1688
1689 std::vector<std::uint8_t> playerPacket;
1690 rnp::Serializer playerPacketSerializer(playerPacket);
1691 playerPacketSerializer.serializeHeader(header);
1692 playerPacketSerializer.serializeGameStart(gameStart);
1693
1694 // Lock clients mutex to safely access client data
1695 std::lock_guard<std::mutex> clientsLock(m_clientsMutex);
1696 auto clientIt = m_clients.find(sessionId);
1697 if (clientIt != m_clients.end())
1698 {
1699 sendPacketImmediate(playerPacketSerializer.getData(), clientIt->second.endpoint);
1700 }
1701 }
1702
1703 utl::Logger::log("AsioServer: Broadcasted game start for lobby " + std::to_string(lobbyId),
1705
1706 // Publish GAME_START event to game logic to start waves
1708 utl::Logger::log("AsioServer: Published GAME_START event to game logic", utl::LogLevel::INFO);
1709 }
1710
1711} // namespace srv
Asio-based UDP server implementation for R-Type multiplayer game networking.
Thread-safe event bus implementation for inter-component communication.
Event structures and types for event-driven communication.
This file contains the Logger class.
This file contains the network protocol.
Security utilities and input validation.
void onPing(PingHandler handler)
Register PING packet handler.
void onPong(PongHandler handler)
Register PONG packet handler.
void onLobbyJoin(LobbyJoinHandler handler)
Register LOBBY_JOIN packet handler.
void onLobbyLeave(LobbyLeaveHandler handler)
Register LOBBY_LEAVE packet handler.
HandlerResult processPacket(const std::vector< std::uint8_t > &data, const PacketContext &context)
Process a received packet.
void onLobbyListRequest(LobbyListRequestHandler handler)
Register LOBBY_LIST_REQUEST packet handler.
void onConnect(ConnectHandler handler)
Register CONNECT packet handler.
void onStartGameRequest(StartGameRequestHandler handler)
Register START_GAME_REQUEST packet handler.
void onLobbyCreate(LobbyCreateHandler handler)
Register LOBBY_CREATE packet handler.
void onEntityEvent(EntityEventHandler handler)
Register ENTITY_EVENT packet handler.
void onDisconnect(DisconnectHandler handler)
Register DISCONNECT packet handler.
void clearSession(std::uint32_t sessionId)
Clear rate limiting data for a session.
Binary serializer for RNP protocol packets.
void serializePingPong(const PacketPingPong &packet)
Serialize PING/PONG packet.
void serializeLobbyCreateResponse(const PacketLobbyCreateResponse &packet)
Serialize LOBBY_CREATE_RESPONSE packet.
PacketHeader deserializeHeader()
Deserialize packet header.
void serializeLobbyListResponse(const PacketLobbyListResponse &packet)
Serialize LOBBY_LIST_RESPONSE packet.
void serializeHeader(const PacketHeader &header)
Serialize packet header.
void serializeConnectAccept(const PacketConnectAccept &packet)
Serialize CONNECT_ACCEPT packet.
void serializeGameStart(const PacketGameStart &packet)
Serialize GAME_START packet.
void serializeDisconnect(const PacketDisconnect &packet)
Serialize DISCONNECT packet.
void serializeLobbyJoinResponse(const PacketLobbyJoinResponse &packet)
Serialize LOBBY_JOIN_RESPONSE packet.
const std::vector< std::uint8_t > & getData() const
Get the serialized data.
void serializeError(const PacketError &packet)
Serialize ERROR packet.
void serializeLobbyUpdate(const PacketLobbyUpdate &packet)
Serialize LOBBY_UPDATE packet.
std::uint16_t m_tickRate
Server tick rate in Hz.
rnp::HandlerResult handlePing(const rnp::PacketPingPong &packet, const rnp::PacketContext &context)
Handle PING packet.
std::unique_ptr< asio::io_context > m_ioContext
ASIO I/O context for async operations.
rnp::HandlerResult handleLobbyJoin(const rnp::PacketLobbyJoin &packet, const rnp::PacketContext &context)
Handle LOBBY_JOIN packet.
void init(const std::string &host, std::uint16_t port) override
void disconnectClient(std::uint32_t sessionId) override
void setupPacketHandlers()
Initialize packet handlers.
void sendToClient(std::uint32_t sessionId, const std::vector< std::uint8_t > &data, bool reliable=false) override
rnp::HandlerResult handleEntityEvent(const std::vector< rnp::EventRecord > &events, const rnp::PacketContext &context) const
Handle entity event packet (including player inputs)
void cleanupEmptyLobbies()
Clean up empty lobbies.
std::uint32_t m_nextLobbyId
Next available lobby ID.
void update() override
rnp::HandlerResult handleConnect(const rnp::PacketConnect &packet, const rnp::PacketContext &context)
Handle CONNECT packet.
~AsioServer() override
Destructor.
void sendConnectAccept(std::uint32_t sessionId, const asio::ip::udp::endpoint &destination)
Send CONNECT_ACCEPT response.
std::string m_host
Server bind address (e.g., "0.0.0.0")
void handleReceive(std::size_t bytesReceived)
Handle received packet.
rnp::LobbyInfo lobbyToLobbyInfo(const Lobby &lobby)
Convert Lobby to LobbyInfo for network transmission.
void setServerCapabilities(std::uint32_t caps) override
void setTickRate(std::uint16_t tickRate) override
std::string endpointToString(const asio::ip::udp::endpoint &endpoint)
Get endpoint string representation.
std::vector< std::uint32_t > getConnectedSessions() const override
rnp::HandlerResult handlePong(const rnp::PacketPingPong &packet, const rnp::PacketContext &context)
Handle PONG packet.
bool isRunning() const override
std::unique_ptr< std::thread > m_networkThread
Dedicated network thread.
std::atomic< bool > m_started
Server started state.
void startReceive()
Start receiving packets asynchronously.
void handleGameOverEvent(const utl::Event &event)
Handle GAME_OVER event from game server.
asio::ip::udp::endpoint m_senderEndpoint
Endpoint of last packet sender.
void sendError(rnp::ErrorCode errorCode, const std::string &description, const asio::ip::udp::endpoint &destination, std::uint32_t sessionId)
Send ERROR packet.
LobbyStatus joinLobby(std::uint32_t lobbyId, std::uint32_t sessionId)
Join player to lobby.
void sendPong(std::uint32_t nonce, const asio::ip::udp::endpoint &destination, std::uint32_t sessionId)
Send PONG response.
std::unordered_map< std::uint32_t, ClientSession > m_clients
Map of session ID to client session.
void networkThreadLoop() const
Network thread main loop.
std::chrono::milliseconds m_clientTimeout
Client timeout duration (15000ms)
std::uint16_t m_port
Server listening port (default: 4567)
std::mutex m_sendQueueMutex
Mutex for send queue access.
std::uint32_t m_serverCaps
Server capability flags.
void sendPacketImmediate(const std::vector< std::uint8_t > &data, const asio::ip::udp::endpoint &destination)
Send packet immediately.
std::uint32_t createLobby(const std::string &lobbyName, std::uint32_t hostSessionId, std::uint8_t maxPlayers, std::uint8_t gameMode)
Create new lobby.
std::mutex m_lobbiesMutex
Mutex for thread-safe lobby access.
void broadcastGameStart(std::uint32_t lobbyId)
Broadcast game start notification to all lobby members.
void sendLobbyCreateResponse(std::uint32_t sessionId, std::uint32_t lobbyId, bool success, rnp::ErrorCode errorCode=rnp::ErrorCode::INTERNAL_ERROR)
Send lobby create response.
std::mutex m_socketMutex
Mutex for socket operations.
AsioServer()
Constructor.
void handleBroadcastEvent(const utl::Event &event)
Handle broadcast event from EventBus.
std::mutex m_clientsMutex
Mutex for thread-safe client access.
std::unordered_map< std::uint32_t, Lobby > m_lobbies
Map of lobby ID to lobby data.
std::queue< QueuedPacket > m_sendQueue
Queue of packets waiting to be sent.
rnp::HandlerResult handleLobbyCreate(const rnp::PacketLobbyCreate &packet, const rnp::PacketContext &context)
Handle LOBBY_CREATE packet.
rnp::HandlerResult handleLobbyListRequest(const rnp::PacketContext &context)
Handle LOBBY_LIST_REQUEST packet.
std::atomic< bool > m_running
Server running state (atomic for thread safety)
std::array< std::uint8_t, MAX_LEN_RECV_BUFFER > m_recvBuffer
Buffer for receiving UDP packets.
void start() override
void sendToAllClients(const std::vector< std::uint8_t > &data, bool reliable=false) override
std::uint32_t m_componentId
Component ID for event bus registration.
void broadcastLobbyUpdate(std::uint32_t lobbyId)
Broadcast lobby update to all lobby members.
void processSendQueue()
Process send queue.
std::unordered_map< std::string, std::uint32_t > m_endpointToSession
Map of endpoint string to session ID.
rnp::HandlerResult handleLobbyLeave(const rnp::PacketContext &context)
Handle LOBBY_LEAVE packet.
void updateClientManagement()
Update client timeouts and send pings.
rnp::HandlerResult handleDisconnect(const rnp::PacketDisconnect &packet, const rnp::PacketContext &context)
Handle DISCONNECT packet.
std::unique_ptr< asio::ip::udp::socket > m_socket
UDP socket for network communication.
std::chrono::milliseconds m_pingInterval
Interval between ping sweeps (5000ms)
void leaveLobby(std::uint32_t sessionId)
Remove player from lobby.
void sendLobbyJoinResponse(std::uint32_t sessionId, std::uint32_t lobbyId, bool success, rnp::ErrorCode errorCode, const rnp::LobbyInfo *lobbyInfo=nullptr)
Send lobby join response.
std::chrono::steady_clock::time_point m_lastPingTime
Timestamp of last ping sweep.
std::size_t getClientCount() const override
void stop() override
rnp::HandlerResult handleStartGameRequest(const rnp::PacketStartGameRequest &packet, const rnp::PacketContext &context)
Handle START_GAME_REQUEST packet.
void broadcastGameOverToLobby(std::uint32_t lobbyId)
Broadcast game start to all players in lobby.
void handleSendToClientEvent(const utl::Event &event)
Handle send to client event from EventBus.
std::unique_ptr< rnp::HandlerPacket > m_packetHandler
RNP packet handler instance.
void sendLobbyList(std::uint32_t sessionId)
Send lobby list to client.
void processEventBusEvents()
Process EventBus events for network operations.
void handleSendEntityEventToClients(const utl::Event &event)
Handle send entity event to clients from EventBus.
utl::EventBus & m_eventBus
std::uint32_t generateSessionId() const
Generate unique session ID.
bool publish(const Event &event)
Publish an event to the bus.
Definition EventBus.hpp:118
std::vector< Event > consumeForTarget(std::uint32_t targetId, std::uint32_t maxEvents=100)
Consume events targeted to specific component.
Definition EventBus.hpp:357
void registerComponent(std::uint32_t componentId, const std::string &name)
Register component name for better debugging.
Definition EventBus.hpp:423
void subscribe(std::uint32_t componentId, EventType type)
Subscribe component to specific event types.
Definition EventBus.hpp:384
Event structure for inter-component communication.
Definition Event.hpp:89
std::vector< std::uint8_t > data
Serialized event data.
Definition Event.hpp:96
static bool isValidLobbyName(const std::string &name)
Validate lobby name for security and format compliance.
Definition Security.hpp:55
static bool isValidPlayerName(const std::string &name)
Validate player name for security and format compliance.
Definition Security.hpp:36
static void log(const std::string &message, const LogLevel &logLevel)
Definition Logger.hpp:51
ErrorCode
Error codes.
Definition Protocol.hpp:75
HandlerResult
Packet processing result.
constexpr std::size_t MAX_PAYLOAD
Definition Protocol.hpp:18
constexpr size_t MAX_CLIENTS
LobbyStatus
Status codes for lobby operations.
@ IN_GAME
Game in progress.
@ NOT_FOUND
Requested lobby does not exist.
@ FULL
Lobby has reached maximum capacity.
@ SUCCESS
Operation completed successfully.
@ ALREADY_IN_LOBBY
Client is already in a lobby.
static constexpr std::uint32_t GAME_LOGIC
Definition Event.hpp:19
static constexpr std::uint32_t NETWORK_SERVER
Definition Event.hpp:18
Lobby information structure.
Definition Protocol.hpp:237
std::uint32_t hostSessionId
Definition Protocol.hpp:244
std::array< char, 32 > lobbyName
Definition Protocol.hpp:239
std::uint8_t status
Definition Protocol.hpp:243
std::array< std::array< char, 32 >, 8 > playerNames
Definition Protocol.hpp:245
std::uint32_t lobbyId
Definition Protocol.hpp:238
std::uint8_t currentPlayers
Definition Protocol.hpp:240
std::uint8_t maxPlayers
Definition Protocol.hpp:241
std::uint8_t gameMode
Definition Protocol.hpp:242
CONNECT_ACCEPT packet payload.
Definition Protocol.hpp:148
std::uint32_t sessionId
Definition Protocol.hpp:149
CONNECT packet payload.
Definition Protocol.hpp:138
std::array< char, 32 > playerName
Definition Protocol.hpp:140
std::uint32_t clientCaps
Definition Protocol.hpp:141
std::uint8_t nameLen
Definition Protocol.hpp:139
Context information for packet processing.
std::uint32_t sessionId
std::chrono::steady_clock::time_point receiveTime
std::string senderAddress
std::uint16_t senderPort
DISCONNECT packet payload.
Definition Protocol.hpp:159
std::uint16_t reasonCode
Definition Protocol.hpp:160
ERROR packet payload.
Definition Protocol.hpp:217
std::uint16_t errorCode
Definition Protocol.hpp:218
GAME_START packet payload.
Definition Protocol.hpp:309
std::uint32_t lobbyId
Definition Protocol.hpp:310
Packet header according to RNP specification (Big Endian) Total size: 7 bytes (1 + 2 + 4)
Definition Protocol.hpp:128
std::uint32_t sessionId
Definition Protocol.hpp:131
std::uint8_t type
Definition Protocol.hpp:129
std::uint16_t length
Definition Protocol.hpp:130
LOBBY_CREATE_RESPONSE packet payload.
Definition Protocol.hpp:272
LOBBY_CREATE packet payload.
Definition Protocol.hpp:261
std::array< char, 32 > lobbyName
Definition Protocol.hpp:263
std::uint8_t maxPlayers
Definition Protocol.hpp:264
std::uint8_t gameMode
Definition Protocol.hpp:265
LOBBY_JOIN_RESPONSE packet payload.
Definition Protocol.hpp:290
LOBBY_JOIN packet payload.
Definition Protocol.hpp:282
std::uint32_t lobbyId
Definition Protocol.hpp:283
LOBBY_LIST_RESPONSE packet payload.
Definition Protocol.hpp:252
std::vector< LobbyInfo > lobbies
Definition Protocol.hpp:254
LOBBY_UPDATE packet payload.
Definition Protocol.hpp:301
PING/PONG packet payload.
Definition Protocol.hpp:208
std::uint32_t sendTimeMs
Definition Protocol.hpp:210
std::uint32_t nonce
Definition Protocol.hpp:209
START_GAME_REQUEST packet payload (client requests to start game)
Definition Protocol.hpp:317
Represents an active client connection session.
bool isConnected
Connection state flag.
std::chrono::steady_clock::time_point lastSeen
Timestamp of last received packet.
std::uint32_t clientCaps
Client capability flags (reserved)
std::uint32_t sessionId
Unique session identifier assigned by server.
std::string playerName
Player's display name.
asio::ip::udp::endpoint endpoint
Client's network endpoint (IP:port)
Represents a game lobby (room) where players gather before starting a game.
std::uint32_t hostSessionId
Session ID of the lobby host.
rnp::LobbyStatus status
Current lobby status.
std::uint8_t maxPlayers
Maximum number of players allowed (1-8)
std::vector< std::uint32_t > playerSessions
List of player session IDs in this lobby.
std::uint32_t lobbyId
Unique lobby identifier.
std::string lobbyName
Human-readable lobby name.
std::uint8_t gameMode
Game mode identifier.
Represents a packet queued for asynchronous transmission.
asio::ip::udp::endpoint destination
Target endpoint.
std::vector< std::uint8_t > data
Serialized packet data.