Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
TcpMessageTransport.cpp
Go to the documentation of this file.
2
3#include "Messaging.hpp"
4#include "VoiceChatPacket.hpp"
5
6#ifdef _WIN32
7#ifndef NOMINMAX
8#define NOMINMAX
9#endif
10#include <winsock2.h>
11#include <ws2tcpip.h>
12#else
13#include <arpa/inet.h>
14#include <cerrno>
15#include <fcntl.h>
16#include <sys/socket.h>
17#include <sys/select.h>
18#include <unistd.h>
19#endif
20
21#include <algorithm>
22#include <array>
23#include <bit>
24#include <chrono>
25#include <deque>
26#include <limits>
27#include <span>
28#include <utility>
29#include <vector>
30
31namespace monopoly::messaging
32{
33 namespace
34 {
35 using Bytes = std::vector<std::uint8_t>;
36 using Clock = std::chrono::steady_clock;
37 constexpr std::size_t MaxBody = 1024 * 1024;
38 constexpr std::size_t MaxBuffered = 4 * MaxBody;
39 constexpr std::size_t MaxPeers = 9; // Host + peers <= ArtLib's 10 receivers.
40 constexpr std::uint32_t Magic = 0x31534D4D; // MMS1, little endian.
41 constexpr std::size_t HeaderSize = 12;
42 enum class Kind : std::uint16_t { Hello = 1, Action = 2, Heartbeat = 3 };
43
44#ifdef _WIN32
45 using Socket = SOCKET;
48 bool pendingError()
49 {
50 const int error = WSAGetLastError();
52 }
54 {
55 u_long enabled = 1;
56 return ioctlsocket(socket, FIONBIO, &enabled) == 0;
57 }
58#else
59 using Socket = int;
60 constexpr Socket InvalidSocket = -1;
61 void closeSocket(Socket socket) { ::close(socket); }
62 bool pendingError()
63 {
64 return errno == EAGAIN || errno == EWOULDBLOCK ||
66 }
68 {
69 if (socket >= FD_SETSIZE) return false;
70 const int flags = fcntl(socket, F_GETFL, 0);
71 if (flags < 0 || fcntl(socket, F_SETFL, flags | O_NONBLOCK) != 0)
72 return false;
73#ifdef SO_NOSIGPIPE
74 const int enabled = 1;
76 &enabled, sizeof(enabled)) != 0)
77 return false;
78#endif
79 return true;
80 }
81#endif
82
83 void put(Bytes& bytes, std::uint64_t value, unsigned count)
84 {
85 for (unsigned index = 0; index < count; ++index)
86 bytes.push_back(static_cast<std::uint8_t>(value >> (8 * index)));
87 }
88
89 struct Reader
90 {
91 std::span<const std::uint8_t> bytes;
92 std::size_t at{};
93 bool valid{true};
94 std::uint64_t get(unsigned count)
95 {
96 if (count > bytes.size() - at)
97 {
98 valid = false;
99 return 0;
100 }
101 std::uint64_t result = 0;
102 for (unsigned i = 0; i < count; ++i)
103 result |= std::uint64_t{bytes[at++]} << (i * 8);
104 return result;
105 }
106 bool blob(Bytes& target)
107 {
108 const auto size = get(4);
109 if (!valid || size > bytes.size() - at)
110 return false;
111 target.assign(bytes.begin() + at, bytes.begin() + at + size);
112 at += static_cast<std::size_t>(size);
113 return true;
114 }
115 };
116
117 Bytes frame(Kind kind, const Bytes& body)
118 {
119 Bytes bytes;
120 bytes.reserve(HeaderSize + body.size());
121 put(bytes, Magic, 4);
122 put(bytes, 1, 2); // Protocol version.
123 put(bytes, static_cast<unsigned>(kind), 2);
124 put(bytes, body.size(), 4);
125 bytes.insert(bytes.end(), body.begin(), body.end());
126 return bytes;
127 }
128
129 Bytes encode(const actions::Message& message)
130 {
131 // No native struct/wchar_t layout crosses the wire. stringA uses
132 // 80 UTF-32 code units, with explicit UTF-16 surrogate conversion.
133 if (message.binaryData.size() > MaxBody ||
134 message.binaryDataA.size() > MaxBody - message.binaryData.size() ||
135 message.binaryData.size() + message.binaryDataA.size() > MaxBody - 372)
136 return {};
137 Bytes body;
138 put(body, static_cast<unsigned>(message.action), 2);
139 put(body, message.fromPlayer, 1);
140 put(body, message.toPlayer, 1);
141 for (auto value : {message.numberA, message.numberB, message.numberC,
142 message.numberD, message.numberE})
143 put(body, static_cast<std::uint64_t>(value), 8);
144 std::array<std::uint32_t, 80> text{};
145 std::size_t out = 0;
146 for (std::size_t i = 0; i < message.stringA.size() &&
147 message.stringA[i] != 0 && out < text.size() - 1; ++i)
148 {
149 auto value = static_cast<std::uint32_t>(message.stringA[i]);
150 if constexpr (sizeof(wchar_t) == 2)
151 {
152 if (value >= 0xD800 && value <= 0xDBFF)
153 {
154 if (++i >= message.stringA.size()) return {};
155 const auto low = static_cast<std::uint32_t>(message.stringA[i]);
156 if (low < 0xDC00 || low > 0xDFFF) return {};
157 value = 0x10000 + ((value - 0xD800) << 10) + low - 0xDC00;
158 }
159 }
160 if (value > 0x10FFFF || (value >= 0xD800 && value <= 0xDFFF))
161 return {};
162 text[out++] = value;
163 }
164 for (auto value : text) put(body, value, 4);
165 for (const auto* blob : {&message.binaryData, &message.binaryDataA})
166 {
167 put(body, blob->size(), 4);
168 body.insert(body.end(), blob->begin(), blob->end());
169 }
170 return frame(Kind::Action, body);
171 }
172
173 bool decode(std::span<const std::uint8_t> body, actions::Message& message)
174 {
175 Reader reader{body};
176 message = {};
177 message.action = static_cast<actions::Type>(reader.get(2));
178 message.fromPlayer = static_cast<rules::PlayerNumber>(reader.get(1));
179 message.toPlayer = static_cast<rules::PlayerNumber>(reader.get(1));
180 for (auto* number : {&message.numberA, &message.numberB, &message.numberC,
181 &message.numberD, &message.numberE})
182 *number = std::bit_cast<std::int64_t>(reader.get(8));
183 std::size_t out = 0;
184 bool ended = false;
185 for (unsigned i = 0; i < 80; ++i)
186 {
187 const auto value = static_cast<std::uint32_t>(reader.get(4));
188 if (value == 0) { ended = true; continue; }
189 if (ended || value > 0x10FFFF || (value >= 0xD800 && value <= 0xDFFF))
190 return false;
191 if constexpr (sizeof(wchar_t) == 2)
192 {
193 if (value >= 0x10000)
194 {
195 if (out + 2 >= message.stringA.size()) return false;
196 message.stringA[out++] = static_cast<wchar_t>(0xD800 + ((value - 0x10000) >> 10));
197 message.stringA[out++] = static_cast<wchar_t>(0xDC00 + ((value - 0x10000) & 1023));
198 continue;
199 }
200 }
201 if (out + 1 >= message.stringA.size()) return false;
202 message.stringA[out++] = static_cast<wchar_t>(value);
203 }
204 return ended && reader.blob(message.binaryData) &&
205 reader.blob(message.binaryDataA) && reader.valid && reader.at == body.size();
206 }
207
208 struct Peer
209 {
211 std::uint32_t id{};
213 bool ready{};
214 bool gameplay{};
215 bool admitted{};
216 bool dead{};
219 std::deque<Bytes> output;
220 std::size_t offset{};
221 std::size_t buffered{};
222 Clock::time_point lastReceive{Clock::now()};
223 Clock::time_point lastSend{Clock::now()};
224 Clock::time_point lastProgress{Clock::now()};
225 };
226
227 class TcpTransport final : public Transport
228 {
229 public:
230 explicit TcpTransport(bool host, TcpSessionMode mode) : host_(host), mode_(mode) {}
231 ~TcpTransport() override
232 {
233 for (auto& peer : peers_) closeSocket(peer.socket);
234 if (listener_ != InvalidSocket) closeSocket(listener_);
235#ifdef _WIN32
236 if (winsock_) WSACleanup();
237#endif
238 }
239
240 bool open(std::string_view address, std::uint16_t port)
241 {
242#ifdef _WIN32
243 WSADATA data{};
244 if (WSAStartup(MAKEWORD(2, 2), &data) != 0)
245 return fail("WSAStartup failed");
246 winsock_ = true;
247#endif
249 endpoint.sin_family = AF_INET;
250 endpoint.sin_port = htons(port);
251 if (port == 0 || inet_pton(AF_INET, std::string(address).c_str(), &endpoint.sin_addr) != 1)
252 return fail("TCP endpoint requires a numeric IPv4 address and port 1..65535");
254 if (socket == InvalidSocket) return fail("TCP socket creation failed");
255 if (!nonblocking(socket))
256 {
258 return fail("TCP nonblocking setup failed");
259 }
260 if (host_)
261 {
262 if (::bind(socket, reinterpret_cast<const sockaddr*>(&endpoint), sizeof(endpoint)) != 0 ||
263 ::listen(socket, static_cast<int>(MaxPeers)) != 0)
264 {
266 return fail("TCP bind/listen failed");
267 }
268 listener_ = socket;
269 }
270 else
271 {
272 const bool connecting = ::connect(socket,
273 reinterpret_cast<const sockaddr*>(&endpoint), sizeof(endpoint)) != 0;
274 if (connecting && !pendingError())
275 {
277 return fail("TCP connect failed");
278 }
279 Peer peer;
280 peer.socket = socket;
281 peer.connecting = connecting;
282 Bytes hello(4, 0);
283 if (mode_ == TcpSessionMode::Gameplay) put(hello, 1, 4);
284 enqueue(peer, frame(Kind::Hello, hello));
285 peers_.push_back(std::move(peer));
286 }
287 return true;
288 }
289
290 bool server() const noexcept override { return host_; }
291 bool gameplayEnabled() const noexcept override { return mode_ == TcpSessionMode::Gameplay; }
292 std::uint32_t localSourceId() const noexcept override { return localSourceId_; }
293 void setPlayerOwner(rules::PlayerNumber player, std::uint32_t source) noexcept override
294 {
295 if (host_ && player < rules::MaxPlayers) owners_[player] = source;
296 }
297 bool sourceConnected(std::uint32_t source) const noexcept override
298 {
299 if (host_ && source == 0) return true;
300 return std::any_of(peers_.begin(), peers_.end(), [&](const Peer& peer) {
301 return peer.ready && !peer.dead && (host_ ? peer.id == source : source == 0);
302 });
303 }
304 bool admitSource(std::uint32_t source) override
305 {
306 if (!host_) return false;
307 for (auto& peer : peers_)
308 if (peer.id == source && peer.ready && !peer.dead)
309 {
310 peer.admitted = true;
311 return true;
312 }
313 return false;
314 }
315 bool receiveDisconnectedSource(std::uint32_t& source) override
316 {
317 if (disconnected_.empty()) return false;
318 source = disconnected_.front();
319 disconnected_.pop_front();
320 return true;
321 }
322 bool sendToSource(const actions::Message& message, std::uint32_t source) override
323 {
324 if (!host_ || !validNotification(message)) return false;
325 const auto bytes = encode(message);
326 if (bytes.empty()) return false;
327 for (auto& peer : peers_)
328 if (peer.id == source && peer.ready && peer.admitted && !peer.dead)
329 {
330 if (!peer.gameplay && message.toPlayer != rules::AllPlayers) return false;
331 if (enqueue(peer, bytes)) return true;
332 drop(peer, "TCP peer outgoing queue exceeded its bound");
333 return false;
334 }
335 return false;
336 }
337 bool active() const noexcept override
338 {
339 if (host_ && gameplayEnabled() && listener_ != InvalidSocket) return true;
340 return std::any_of(peers_.begin(), peers_.end(),
341 [](const Peer& peer) { return peer.ready && !peer.dead; });
342 }
343 std::string_view error() const noexcept override { return error_; }
344 std::size_t queued() const noexcept override
345 {
346 std::size_t count = 0;
347 for (const auto& peer : peers_)
348 if (!peer.dead) count = std::max(count, peer.output.size());
349 return count;
350 }
351
352 bool receive(actions::Message& message) override
353 {
354 if (incoming_.empty()) return false;
355 message = std::move(incoming_.front());
356 incoming_.pop_front();
357 return true;
358 }
359
360 bool send(const actions::Message& message) override
361 {
362 // Evicting the final slow peer during a RULE batch must not
363 // reject subsequent local bank notifications before next pump.
364 if (!active()) return host_;
365 if (!host_ && !(gameplayEnabled() ? validGameplayAction(message) : validVoice(message))) return false;
366 if (host_ && !validNotification(message)) return false;
367 // Spectator is not a unique connection. Explicit recipients
368 // use sendToSource instead of leaking a private notification.
369 if (host_ && message.toPlayer == rules::SpectatorPlayer) return false;
370 const Bytes bytes = encode(message);
371 if (bytes.empty())
372 {
373 if (host_)
374 for (auto& peer : peers_)
375 if (!peer.dead) drop(peer, "TCP action cannot be encoded");
376 return host_;
377 }
378 bool sent = false;
379 for (auto& peer : peers_)
380 {
381 if (!peer.ready || peer.dead || (host_ && !peer.admitted)) continue;
382 if (host_ && !peer.gameplay && message.toPlayer != rules::AllPlayers) continue;
383 if (host_ && message.toPlayer < rules::MaxPlayers &&
384 owners_[message.toPlayer] != peer.id) continue;
385 if (!enqueue(peer, bytes))
386 drop(peer, "TCP peer outgoing queue exceeded its bound");
387 else sent = true;
388 }
389 // A host still delivers locally if the last slow peer was
390 // evicted. A client must report failure to its capture owner.
391 return host_ || sent;
392 }
393
394 void pump() override
395 {
396 if (listener_ != InvalidSocket && peers_.size() < MaxPeers)
397 {
398 // One accept per cycle, so connection floods cannot starve SDL.
399 Socket socket = ::accept(listener_, nullptr, nullptr);
400 if (socket != InvalidSocket)
401 {
402 if (!nonblocking(socket) || nextId_ == 0)
404 else
405 {
406 Peer peer;
407 peer.socket = socket;
408 peer.id = nextId_++;
409 peers_.push_back(std::move(peer));
410 }
411 }
412 else if (!pendingError()) fail("TCP accept failed");
413 }
414
415 for (auto& peer : peers_)
416 {
417 if (!peer.dead) service(peer);
418 // A synthetic STOP carries the authenticated source of a
419 // departed peer through the same RULE echo path as audio.
420 // Keep the dead peer until there is room for that cleanup.
421 if (peer.dead && host_ && peer.ready && incoming_.size() < MessageQueueCapacity &&
422 disconnected_.size() < MessageQueueCapacity)
423 {
424 actions::Message stop;
425 stop.action = actions::Type::VoiceChat;
426 stop.fromPlayer = rules::SpectatorPlayer;
427 stop.toPlayer = rules::BankPlayer;
428 stop.numberA = rules::AllPlayers;
429 stop.sourceId = peer.id;
430 (void)voicechat::packet::makeStopPacket(stop.binaryDataA);
431 incoming_.push_back(std::move(stop));
432 // Only this socket owner can report a departed source.
433 // MESS expands the identity to all of its player slots.
434 disconnected_.push_back(peer.id);
435 peer.ready = false;
436 }
437 }
438 std::erase_if(peers_, [&](Peer& peer)
439 {
440 if (!peer.dead || (host_ && peer.ready)) return false;
441 closeSocket(peer.socket);
442 return true;
443 });
444 }
445
446 private:
447 bool fail(std::string text) { error_ = std::move(text); return false; }
448 void drop(Peer& peer, const char* reason)
449 {
450 peer.dead = true;
451 fail(reason);
452 // A disconnected client must not replay old buffered actions.
453 if (!host_)
454 {
455 incoming_.clear();
456 localSourceId_ = 0;
457 }
458 }
459 bool enqueue(Peer& peer, const Bytes& bytes)
460 {
461 if (peer.output.size() >= MessageQueueCapacity ||
462 bytes.size() > MaxBuffered - peer.buffered) return false;
463 if (peer.output.empty()) peer.lastProgress = Clock::now();
464 peer.buffered += bytes.size();
465 peer.output.push_back(bytes);
466 return true;
467 }
468 bool validVoice(const actions::Message& message) const
469 {
470 return message.action == actions::Type::VoiceChat &&
471 message.fromPlayer == rules::SpectatorPlayer &&
472 message.toPlayer == rules::BankPlayer &&
473 message.numberA == rules::AllPlayers && message.numberB == 0 &&
474 message.numberC == 0 && message.numberD == 0 && message.numberE == 0 &&
475 message.stringA[0] == 0 && message.binaryData.empty() &&
477 }
478
479 bool validNotification(const actions::Message& message) const
480 {
481 const auto action = static_cast<unsigned>(message.action);
482 if (message.fromPlayer != rules::BankPlayer || action < 80u || action > 136u)
483 return false;
484 if (!gameplayEnabled()) return message.toPlayer == rules::AllPlayers;
485 return message.toPlayer == rules::AllPlayers ||
486 message.toPlayer == rules::SpectatorPlayer || message.toPlayer < rules::MaxPlayers;
487 }
488
489 bool validGameplayAction(const actions::Message& message) const
490 {
491 if (message.toPlayer != rules::BankPlayer ||
492 (message.fromPlayer >= rules::MaxPlayers &&
493 message.fromPlayer != rules::SpectatorPlayer)) return false;
494 const auto action = static_cast<unsigned>(message.action);
495 // Rule.h calls movement/landing internal; never admit bank
496 // operations or manufactured disconnect/tick/seed messages.
497 if (message.action == actions::Type::DisconnectedPlayer)
498 return message.fromPlayer < rules::MaxPlayers &&
499 message.numberA == message.fromPlayer;
500 if (action == 0 || action > 50 || action == 2 ||
501 action == 3 || action == 12 || (action >= 19 && action <= 22))
502 return false;
503 if (message.fromPlayer == rules::SpectatorPlayer &&
504 message.action != actions::Type::NamePlayer &&
505 message.action != actions::Type::ResyncClient &&
506 message.action != actions::Type::TextChat &&
507 message.action != actions::Type::VoiceChat) return false;
508 if (message.action == actions::Type::VoiceChat)
509 return (message.numberA == rules::AllPlayers ||
510 (message.numberA >= 0 && message.numberA < rules::MaxPlayers)) &&
511 message.numberB == 0 && message.numberC == 0 &&
512 message.numberD == 0 && message.numberE == 0 &&
513 message.stringA[0] == 0 && message.binaryData.empty() &&
515 return true; // Identity/ownership and phase validation belong to MESS/RULE.
516 }
517
518 bool consume(Peer& peer, Kind kind, std::span<const std::uint8_t> body)
519 {
520 if (!peer.ready)
521 {
522 if (kind != Kind::Hello || (body.size() != 4 && body.size() != 8)) return false;
523 Reader reader{body};
524 const auto id = static_cast<std::uint32_t>(reader.get(4));
525 const auto capability = body.size() == 8 ? reader.get(4) : 0;
526 if (capability > 1) return false;
527 if (host_)
528 {
529 if (id != 0 || (capability == 1 && !gameplayEnabled())) return false;
530 peer.gameplay = capability == 1;
531 peer.admitted = !gameplayEnabled();
533 put(welcome, peer.id, 4);
534 if (body.size() == 8) put(welcome, capability, 4);
535 if (!enqueue(peer, frame(Kind::Hello, welcome))) return false;
536 // Admission causes one explicit resync to all peers.
537 // Existing microphones restart and announce CHAT again.
538 actions::Message resync;
539 resync.action = actions::Type::ResyncClient;
540 resync.fromPlayer = rules::SpectatorPlayer;
541 resync.toPlayer = rules::BankPlayer;
542 resync.numberA = rules::AllPlayers;
543 resync.sourceId = peer.id;
544 incoming_.push_back(std::move(resync));
545 }
546 else
547 {
548 if (id == 0 || (capability == 1) != gameplayEnabled()) return false;
549 peer.id = id;
550 peer.gameplay = capability == 1;
551 localSourceId_ = id;
552 }
553 peer.ready = true;
554 return true;
555 }
556 if (kind == Kind::Heartbeat) return body.empty();
557 if (kind != Kind::Action) return false;
558 actions::Message message;
559 if (!decode(body, message)) return false;
560 if (host_)
561 {
562 if (!(peer.gameplay ? validGameplayAction(message) : validVoice(message))) return false;
563 message.sourceId = peer.id;
564 }
565 else
566 {
567 if (!validNotification(message)) return false;
568 if (message.action == actions::Type::NotifyVoiceChat &&
569 (message.numberD < 0 || message.numberD > std::numeric_limits<std::uint32_t>::max() ||
571 return false;
572 }
573 incoming_.push_back(std::move(message));
574 return true;
575 }
576
577 void service(Peer& peer)
578 {
579 const auto now = Clock::now();
580 if (now - peer.lastReceive > std::chrono::seconds(peer.ready ? 30 : 10) ||
581 (!peer.output.empty() && now - peer.lastProgress > std::chrono::seconds(15)))
582 {
583 drop(peer, "TCP peer timed out");
584 return;
585 }
586 if (peer.connecting)
587 {
590 FD_SET(peer.socket, &writes); FD_SET(peer.socket, &errors);
592#ifdef _WIN32
593 const int selected = ::select(0, nullptr, &writes, &errors, &timeout);
594#else
595 const int selected = ::select(peer.socket + 1, nullptr, &writes, &errors, &timeout);
596#endif
597 if (selected < 0) { drop(peer, "TCP connection polling failed"); return; }
598 if (selected == 0) return;
599 int status = 0;
600#ifdef _WIN32
601 int length = sizeof(status);
602#else
603 socklen_t length = sizeof(status);
604#endif
605 if (getsockopt(peer.socket, SOL_SOCKET, SO_ERROR,
606 reinterpret_cast<char*>(&status), &length) != 0 || status != 0)
607 { drop(peer, "TCP connection failed"); return; }
608 peer.connecting = false;
609 }
610 if (peer.ready && peer.output.empty() && now - peer.lastSend > std::chrono::seconds(5))
611 (void)enqueue(peer, frame(Kind::Heartbeat, {}));
612 if (!peer.output.empty())
613 {
614 const auto& bytes = peer.output.front();
615 int flags = 0;
616#ifdef MSG_NOSIGNAL
617 flags = MSG_NOSIGNAL;
618#endif
619 const auto sent = ::send(peer.socket,
620 reinterpret_cast<const char*>(bytes.data() + peer.offset),
621 static_cast<int>(std::min<std::size_t>(bytes.size() - peer.offset, 64 * 1024)), flags);
622 if (sent < 0 && !pendingError()) { drop(peer, "TCP send failed"); return; }
623 if (sent > 0)
624 {
625 peer.offset += static_cast<std::size_t>(sent);
626 peer.buffered -= static_cast<std::size_t>(sent);
627 peer.lastSend = peer.lastProgress = now;
628 if (peer.offset == bytes.size()) { peer.output.pop_front(); peer.offset = 0; }
629 }
630 }
631
632 if (incoming_.size() >= MessageQueueCapacity) return;
633 std::array<std::uint8_t, 64 * 1024> buffer{};
634 const auto room = MaxBody + HeaderSize - peer.input.size();
635 if (room > 0 && !peer.readClosed)
636 {
637 const auto received = ::recv(peer.socket, reinterpret_cast<char*>(buffer.data()),
638 static_cast<int>(std::min(room, buffer.size())), 0);
639 if (received == 0) peer.readClosed = true;
640 if (received < 0 && !pendingError()) { drop(peer, "TCP receive failed"); return; }
641 if (received > 0)
642 {
643 peer.input.insert(peer.input.end(), buffer.begin(), buffer.begin() + received);
644 // Only complete valid frames refresh the timeout below.
645 }
646 }
647 std::size_t consumed = 0;
648 unsigned budget = 16;
649 while (budget-- > 0 && incoming_.size() < MessageQueueCapacity &&
650 peer.input.size() - consumed >= HeaderSize)
651 {
652 const std::span<const std::uint8_t> input(peer.input);
653 Reader header{input.subspan(consumed, HeaderSize)};
654 if (header.get(4) != Magic || header.get(2) != 1)
655 { drop(peer, "TCP protocol signature/version mismatch"); return; }
656 const auto kind = static_cast<Kind>(header.get(2));
657 const auto size = static_cast<std::size_t>(header.get(4));
658 if (size > MaxBody) { drop(peer, "TCP frame exceeds size limit"); return; }
659 // Admission occurs at MESS's empty-queue boundary. Keep
660 // commands in this bounded per-peer buffer until then;
661 // other peers can still make progress during this pump.
662 if (host_ && gameplayEnabled() && peer.ready &&
663 !peer.admitted && kind == Kind::Action) break;
664 if (peer.input.size() - consumed - HeaderSize < size) break;
665 if (!consume(peer, kind, input.subspan(consumed + HeaderSize, size)))
666 { drop(peer, "TCP frame rejected"); return; }
667 peer.lastReceive = now;
668 consumed += HeaderSize + size;
669 }
670 peer.input.erase(peer.input.begin(), peer.input.begin() + consumed);
671 if (peer.readClosed && (peer.input.empty() ||
672 ((!host_ || !gameplayEnabled() || peer.admitted) && consumed == 0)))
673 drop(peer, "TCP peer disconnected");
674 }
675
676 bool host_{};
678 std::uint32_t localSourceId_{};
679 std::array<std::uint32_t, rules::MaxPlayers> owners_{};
680#ifdef _WIN32
681 bool winsock_{};
682#endif
683 Socket listener_{InvalidSocket};
684 std::uint32_t nextId_{1};
685 std::vector<Peer> peers_;
686 std::deque<actions::Message> incoming_;
687 std::deque<std::uint32_t> disconnected_;
688 std::string error_;
689 };
690 }
691
692 std::expected<std::unique_ptr<Transport>, std::string> openTcpTransport(
693 bool host, std::string_view address, std::uint16_t port, TcpSessionMode mode)
694 {
695 auto transport = std::make_unique<TcpTransport>(host, mode);
696 if (!transport->open(address, port))
697 return std::unexpected(std::string(transport->error()));
698 return std::unique_ptr<Transport>(std::move(transport));
699 }
700}
std::size_t count
std::vector< std::uint8_t > data
std::size_t offset
const char * header
std::string_view text
std::vector< std::byte > bytes
bool enabled
Definition Engine.cpp:170
data::DataId id
std::shared_ptr< const data::HmdTextureImage > source
unsigned index
Kind kind
std::size_t frame
SDL_IOStream * errors
std::string error
bool connecting
bool valid
Clock::time_point lastReceive
Clock::time_point lastSend
Socket socket
Bytes input
bool admitted
std::deque< Bytes > output
bool readClosed
bool gameplay
std::size_t at
std::size_t buffered
Clock::time_point lastProgress
std::uint16_t value
Definition Timers.cpp:16
data::DataId out
bool encode(const State &state, std::vector< std::uint8_t > &result) noexcept
bool decode(std::span< const std::uint8_t > data, State &result) noexcept
constexpr std::size_t MessageQueueCapacity
Definition Messaging.hpp:14
std::expected< std::unique_ptr< Transport >, std::string > openTcpTransport(bool host, std::string_view address, std::uint16_t port, TcpSessionMode mode)
void setPlayerOwner(rules::PlayerNumber player, std::uint32_t source)
Definition Messaging.cpp:76
void send(const GameState &state, const actions::Message &message, std::int64_t error)
constexpr PlayerNumber BankPlayer
Definition RuleTypes.hpp:15
std::uint8_t PlayerNumber
Definition RuleTypes.hpp:12
constexpr PlayerNumber SpectatorPlayer
Definition RuleTypes.hpp:22
constexpr PlayerNumber MaxPlayers
Definition RuleTypes.hpp:14
constexpr PlayerNumber AllPlayers
Definition RuleTypes.hpp:17
bool receive(Message &message)
std::size_t size()
ParseStatus parse(std::span< const std::uint8_t > bytes, std::uint32_t sourceId, EventSink sink=nullptr) noexcept
bool makeStopPacket(std::vector< std::uint8_t > &packet)