1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2013-2018, The OpenClonk Team and contributors
6 * Copyright (c) 2017-2022, The LegacyClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18#include <C4Include.h>
19#include <C4Network2IO.h>
20#include <C4Network2Reference.h>
21
22#include <C4Network2Discover.h>
23#include "C4Network2UPnP.h"
24#include <C4Application.h>
25#include <C4UserMessages.h>
26#include <C4Log.h>
27#include <C4Game.h>
28
29#ifndef _WIN32
30#include <sys/socket.h>
31#include <netinet/in.h>
32#include <arpa/inet.h>
33#endif
34
35#include <cassert>
36#include <concepts>
37
38// internal structures
39struct C4Network2IO::NetEvPacketData
40{
41 C4NetIOPacket Packet;
42 C4Network2IOConnection *Conn;
43};
44
45// compile options
46#define C4NET2IO_DUMP_LEVEL 0
47
48// *** C4Network2IO
49
50C4Network2IO::C4Network2IO()
51 : pNetIO_TCP(nullptr), pNetIO_UDP(nullptr),
52 pNetIODiscover(nullptr), pRefServer(nullptr),
53 pConnList(nullptr),
54 iNextConnID(0),
55 fAllowConnect(false),
56 fExclusiveConn(false),
57 pAutoAcceptList(nullptr),
58 iLastPing(0), iLastExecute(0), iLastStatistic(0),
59 iTCPIRate(0), iTCPORate(0), iTCPBCRate(0),
60 iUDPIRate(0), iUDPORate(0), iUDPBCRate(0)
61{
62}
63
64C4Network2IO::~C4Network2IO()
65{
66 Clear();
67}
68
69template<std::derived_from<C4NetIO> T>
70static T *CreateNetIO(const std::shared_ptr<spdlog::logger> &logger, const char *const name, T *const io, const std::uint16_t port, C4InteractiveThread &thread)
71{
72 std::unique_ptr<T> netIO{io};
73
74 if (port <= 0)
75 {
76 return nullptr;
77 }
78
79 if (netIO->Init(port))
80 {
81 logger->info(fmt: "{} initialized on port {}", args: name, args: port);
82 thread.AddProc(pProc: netIO.get());
83 return netIO.release();
84 }
85 else
86 {
87 const char *const error{netIO->GetError()};
88 logger->error(fmt: "could not init {} ({})", args: name, args: error ? error : "");
89 return nullptr;
90 }
91}
92
93bool C4Network2IO::Init(const std::uint16_t iPortTCP, const std::uint16_t iPortUDP, const std::uint16_t iPortDiscover, const std::uint16_t iPortRefServer) // by main thread
94{
95 // Already initialized? Clear first
96 if (pNetIO_TCP || pNetIO_UDP) Clear();
97
98 // init members
99 logger = Application.LogSystem.CreateLogger(config&: Config.Logging.Network2IO);
100 iLastPing = iLastStatistic = timeGetTime();
101 iTCPIRate = iTCPORate = iTCPBCRate = 0;
102 iUDPIRate = iUDPORate = iUDPBCRate = 0;
103
104 // init event callback
105 C4InteractiveThread &Thread = Application.InteractiveThread;
106 Thread.SetCallback(eEvent: Ev_Net_Conn, pnNetworkCallback: this);
107 Thread.SetCallback(eEvent: Ev_Net_Disconn, pnNetworkCallback: this);
108 Thread.SetCallback(eEvent: Ev_Net_Packet, pnNetworkCallback: this);
109
110 // initialize UPnP
111 if (Config.Network.EnableUPnP)
112 {
113 UPnP = std::make_unique<C4Network2UPnP>();
114 }
115
116 // initialize net i/o classes: TCP first
117 pNetIO_TCP = CreateNetIO(logger, name: "TCP I/O", io: new C4NetIOTCP{}, port: iPortTCP, thread&: Thread);
118 if (pNetIO_TCP)
119 {
120 pNetIO_TCP->SetCallback(this);
121
122 if (Config.Network.EnableUPnP)
123 {
124 UPnP->AddMapping(protocol: P_TCP, internalPort: iPortTCP, externalPort: 0);
125 }
126 }
127
128 // then UDP
129 pNetIO_UDP = CreateNetIO(logger, name: "UDP I/O", io: new C4NetIOUDP{}, port: iPortUDP, thread&: Thread);
130 if (pNetIO_UDP)
131 {
132 pNetIO_UDP->SetCallback(this);
133
134 if (Config.Network.EnableUPnP)
135 {
136 UPnP->AddMapping(protocol: P_UDP, internalPort: iPortUDP, externalPort: 0);
137 }
138 }
139
140 // no protocols?
141 if (!pNetIO_TCP && !pNetIO_UDP)
142 {
143 LogFatalNTr(message: "Network: fatal - no protocols available!");
144 Thread.ClearCallback(eEvent: Ev_Net_Conn, pnNetworkCallback: this);
145 Thread.ClearCallback(eEvent: Ev_Net_Disconn, pnNetworkCallback: this);
146 Thread.ClearCallback(eEvent: Ev_Net_Packet, pnNetworkCallback: this);
147 return false;
148 }
149
150 // discovery last
151 if (iPortDiscover > 0)
152 {
153 pNetIODiscover = new C4Network2IODiscover(iPortRefServer);
154 pNetIODiscover->SetDiscoverable(false);
155
156 pNetIODiscover = CreateNetIO(logger, name: "discovery", io: pNetIODiscover, port: iPortDiscover, thread&: Thread);
157 }
158
159 // plus reference server
160 pRefServer = CreateNetIO(logger, name: "reference server", io: new C4Network2RefServer{}, port: iPortRefServer, thread&: Thread);
161
162 // own timer
163 iLastExecute = timeGetTime();
164 Thread.AddProc(pProc: this);
165
166 // ok
167 return true;
168}
169
170void C4Network2IO::Clear() // by main thread
171{
172 // process remaining events
173 C4InteractiveThread &Thread = Application.InteractiveThread;
174 Thread.ProcessEvents();
175 // clear event callbacks
176 Thread.ClearCallback(eEvent: Ev_Net_Conn, pnNetworkCallback: this);
177 Thread.ClearCallback(eEvent: Ev_Net_Disconn, pnNetworkCallback: this);
178 Thread.ClearCallback(eEvent: Ev_Net_Packet, pnNetworkCallback: this);
179 // close all connections
180 CStdLock ConnListLock(&ConnListCSec);
181 for (C4Network2IOConnection *pConn = pConnList, *pNext; pConn; pConn = pNext)
182 {
183 pNext = pConn->pNext;
184 // close
185 pConn->Close();
186 RemoveConnection(pConn);
187 }
188 // reset list
189 pConnList = nullptr;
190 ConnListLock.Clear();
191 // close net i/o classes
192 Thread.RemoveProc(pProc: this);
193 if (pNetIODiscover) { Thread.RemoveProc(pProc: pNetIODiscover); delete pNetIODiscover; pNetIODiscover = nullptr; }
194 if (pNetIO_TCP) { Thread.RemoveProc(pProc: pNetIO_TCP); delete pNetIO_TCP; pNetIO_TCP = nullptr; }
195 if (pNetIO_UDP) { Thread.RemoveProc(pProc: pNetIO_UDP); delete pNetIO_UDP; pNetIO_UDP = nullptr; }
196 if (pRefServer) { Thread.RemoveProc(pProc: pRefServer); delete pRefServer; pRefServer = nullptr; }
197 // clear UPnP
198 UPnP.reset();
199 // remove auto-accepts
200 ClearAutoAccept();
201 // reset flags
202 fAllowConnect = fExclusiveConn = false;
203 // reset connection ID
204 iNextConnID = 0;
205 logger.reset();
206}
207
208void C4Network2IO::SetLocalCCore(const C4ClientCore &nCCore)
209{
210 CStdLock LCCoreLock(&LCCoreCSec);
211 LCCore = nCCore;
212}
213
214C4NetIO *C4Network2IO::MsgIO() // by both
215{
216 if (pNetIO_UDP) return pNetIO_UDP;
217 if (pNetIO_TCP) return pNetIO_TCP;
218 return nullptr;
219}
220
221C4NetIO *C4Network2IO::DataIO() // by both
222{
223 if (pNetIO_TCP) return pNetIO_TCP;
224 if (pNetIO_UDP) return pNetIO_UDP;
225 return nullptr;
226}
227
228bool C4Network2IO::Connect(const C4NetIO::addr_t &addr, const C4Network2IOProtocol prot, const C4ClientCore &ccore, const char *const password) // by main thread
229{
230 return ConnectWithSocket(addr, eProt: prot, nCCore: ccore, socket: nullptr, szPassword: password);
231}
232
233bool C4Network2IO::ConnectWithSocket(const C4NetIO::addr_t &addr, C4Network2IOProtocol eProt, const C4ClientCore &nCCore, std::unique_ptr<C4NetIOTCP::Socket> socket, const char *szPassword) // by main thread
234{
235 // get network class
236 C4NetIO *pNetIO = getNetIO(eProt);
237 if (!pNetIO) return false;
238 // already connected/connecting?
239 if (GetConnectionByConnAddr(addr, pNetIO)) return true;
240 // assign new connection ID, peer address isn't known yet
241 uint32_t iConnID = iNextConnID++;
242 C4NetIO::addr_t paddr;
243 // create connection object and add to list
244 C4Network2IOConnection *pConn = new C4Network2IOConnection();
245 pConn->Set(pnNetClass: pNetIO, eProt, nPeerAddr: paddr, nConnectAddr: addr, nStatus: CS_Connect, szPassword, iID: iConnID);
246 pConn->SetCCore(nCCore);
247 if (socket)
248 pConn->SetSocket(std::move(socket));
249 AddConnection(pConn);
250 // connect
251 if (!pConn->Connect())
252 {
253 // log error as warning - it's not of the same severity as other Network2IO errors
254 // and may happen rather frequently with no impact to the player
255 logger->warn(fmt: "could not connect to {} using {}: {}", args: addr.ToString(),
256 args: getNetIOName(pNetIO), args: pNetIO->GetError() ? pNetIO->GetError() : "");
257 pNetIO->ResetError();
258 // remove class
259 RemoveConnection(pConn);
260 return false;
261 }
262 // ok, wait for connection
263 return true;
264}
265
266void C4Network2IO::SetAcceptMode(bool fnAllowConnect) // by main thread
267{
268 fAllowConnect = fnAllowConnect;
269 // Allow connect? Allow discovery of this host
270 if (fAllowConnect)
271 {
272 if (pNetIODiscover)
273 {
274 pNetIODiscover->SetDiscoverable(true);
275 pNetIODiscover->Announce();
276 }
277 }
278}
279
280void C4Network2IO::SetExclusiveConnMode(bool fnExclusiveConn) // by main thread
281{
282 if (fExclusiveConn == fnExclusiveConn)
283 return;
284 // Set flag
285 fExclusiveConn = fnExclusiveConn;
286 // Allowed? Send all pending welcome packets
287 if (!fExclusiveConn)
288 SendConnPackets();
289}
290
291int C4Network2IO::getConnectionCount() // by main thread
292{
293 int iCount = 0;
294 CStdLock ConnListLock(&ConnListCSec);
295 for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext)
296 if (!pConn->isClosed())
297 iCount++;
298 return iCount;
299}
300
301void C4Network2IO::ClearAutoAccept() // by main thread
302{
303 CStdLock AALock(&AutoAcceptCSec);
304 // delete
305 while (pAutoAcceptList)
306 {
307 // remove
308 AutoAccept *pAcc = pAutoAcceptList;
309 pAutoAcceptList = pAcc->Next;
310 // delete
311 delete pAcc;
312 }
313}
314
315void C4Network2IO::AddAutoAccept(const C4ClientCore &CCore) // by main thread
316{
317 CStdLock AALock(&AutoAcceptCSec);
318 // create
319 AutoAccept *pAcc = new AutoAccept();
320 pAcc->CCore = CCore;
321 // add
322 pAcc->Next = pAutoAcceptList;
323 pAutoAcceptList = pAcc;
324}
325
326void C4Network2IO::RemoveAutoAccept(const C4ClientCore &CCore) // by main thread
327{
328 CStdLock AALock(&AutoAcceptCSec);
329 // find & remove
330 AutoAccept *pAcc = pAutoAcceptList, *pLast = nullptr;
331 while (pAcc)
332 if (pAcc->CCore.getDiffLevel(CCore2: CCore) <= C4ClientCoreDL_IDMatch)
333 {
334 // unlink
335 AutoAccept *pDelete = pAcc;
336 pAcc = pAcc->Next;
337 (pLast ? pLast->Next : pAutoAcceptList) = pAcc;
338 // delete
339 delete pDelete;
340 }
341 else
342 {
343 // next peer
344 pLast = pAcc;
345 pAcc = pAcc->Next;
346 }
347}
348
349C4Network2IOConnection *C4Network2IO::GetMsgConnection(int iClientID) // by main thread
350{
351 CStdLock ConnListLock(&ConnListCSec);
352 C4Network2IOConnection *pRes = nullptr;
353 for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext)
354 if (pConn->isAccepted())
355 if (pConn->getClientID() == iClientID)
356 if (pConn->getProtocol() == P_UDP || !pRes)
357 pRes = pConn;
358 // add reference
359 if (pRes) pRes->AddRef();
360 return pRes;
361}
362
363C4Network2IOConnection *C4Network2IO::GetDataConnection(int iClientID) // by main thread
364{
365 CStdLock ConnListLock(&ConnListCSec);
366 C4Network2IOConnection *pRes = nullptr;
367 for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext)
368 if (pConn->isAccepted())
369 if (pConn->getClientID() == iClientID)
370 if (pConn->getProtocol() == P_TCP || !pRes)
371 pRes = pConn;
372 // add reference
373 if (pRes) pRes->AddRef();
374 return pRes;
375}
376
377void C4Network2IO::BeginBroadcast(bool fSelectAll)
378{
379 // lock
380 BroadcastCSec.Enter();
381 // reset all broadcast flags
382 CStdLock ConnListLock(&ConnListCSec);
383 for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext)
384 if (pConn->isOpen())
385 pConn->SetBroadcastTarget(fSelectAll);
386}
387
388void C4Network2IO::EndBroadcast()
389{
390 // unlock
391 BroadcastCSec.Leave();
392}
393
394bool C4Network2IO::Broadcast(const C4NetIOPacket &rPkt)
395{
396 bool fSuccess = true;
397 // There is no broadcasting atm, emulate it
398 CStdLock ConnListLock(&ConnListCSec);
399 for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext)
400 if (pConn->isOpen() && pConn->isBroadcastTarget())
401 fSuccess &= pConn->Send(rPkt);
402 assert(fSuccess);
403 return fSuccess;
404}
405
406bool C4Network2IO::BroadcastMsg(const C4NetIOPacket &rPkt) // by both
407{
408 // TODO: ugly algorithm. do better
409
410 // begin broadcast
411 BeginBroadcast(fSelectAll: false);
412 // select one connection per reachable client
413 CStdLock ConnListLock(&ConnListCSec);
414 for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext)
415 if (pConn->isAccepted())
416 if (pConn->getProtocol() == P_UDP)
417 pConn->SetBroadcastTarget(true);
418 else if (pConn->getProtocol() == P_TCP)
419 {
420 C4Network2IOConnection *pConn2 = GetMsgConnection(iClientID: pConn->getClientID());
421 if (pConn == pConn2)
422 pConn->SetBroadcastTarget(true);
423 pConn2->DelRef();
424 }
425 // send
426 bool fSuccess = Broadcast(rPkt);
427 // end broadcast
428 EndBroadcast();
429 // return
430 return fSuccess;
431}
432
433bool C4Network2IO::InitPuncher(const C4NetIO::addr_t puncherAddr)
434{
435 // UDP must be initialized
436 if (!pNetIO_UDP)
437 return false;
438 // save address
439 switch (puncherAddr.GetFamily())
440 {
441 case C4Network2HostAddress::IPv4:
442 PuncherAddrIPv4 = puncherAddr;
443 break;
444 case C4Network2HostAddress::IPv6:
445 PuncherAddrIPv6 = puncherAddr;
446 break;
447 default:
448 assert(!"Unexpected address family");
449 }
450 // let's punch
451 return pNetIO_UDP->Connect(addr: puncherAddr);
452}
453
454void C4Network2IO::Punch(const C4NetIO::addr_t &puncheeAddr)
455{
456 if (!pNetIO_UDP)
457 return;
458 dynamic_cast<C4NetIOUDP *>(pNetIO_UDP)->SendDirect(packet: MkC4NetIOPacket(cStatus: PID_Pong, Pkt: C4PacketPing{}, addr: puncheeAddr));
459}
460
461void C4Network2IO::SendPuncherPacket(const C4NetpuncherPacket &p, const C4Network2HostAddress::AddressFamily family)
462{
463 if (!pNetIO_UDP) return;
464 if (family == C4Network2HostAddress::IPv4 && !PuncherAddrIPv4.IsNull())
465 pNetIO_UDP->Send(rPacket: p.PackTo(PuncherAddrIPv4));
466 else if (family == C4Network2HostAddress::IPv6 && !PuncherAddrIPv6.IsNull())
467 pNetIO_UDP->Send(rPacket: p.PackTo(PuncherAddrIPv6));
468}
469
470bool C4Network2IO::IsPuncherAddr(const C4NetIO::addr_t &addr) const
471{
472 return
473 (!PuncherAddrIPv4.IsNull() && PuncherAddrIPv4 == addr) ||
474 (!PuncherAddrIPv6.IsNull() && PuncherAddrIPv6 == addr);
475}
476
477// C4NetIO interface
478bool C4Network2IO::OnConn(const C4NetIO::addr_t &PeerAddr, const C4NetIO::addr_t &ConnectAddr, const C4NetIO::addr_t *pOwnAddr, C4NetIO *pNetIO)
479{
480 // Puncher answer?
481 if (pNetIO == pNetIO_UDP && IsPuncherAddr(addr: ConnectAddr))
482 {
483 // Got an address?
484 if (pOwnAddr)
485 Game.Network.OnPuncherConnect(addr: *pOwnAddr);
486 return true;
487 }
488#if (C4NET2IO_DUMP_LEVEL > 1)
489 unsigned int iTime = timeGetTime();
490 logger->debug("OnConn: {}:{:02}:{:02}:{:03}: {}",
491 (iTime / 1000 / 60 / 60), (iTime / 1000 / 60) % 60, (iTime / 1000) % 60, iTime % 1000,
492 getNetIOName(pNetIO));
493#endif
494 // search connection
495 C4Network2IOConnection *pConn = nullptr;
496 if (!ConnectAddr.IsNull())
497 pConn = GetConnectionByConnAddr(addr: ConnectAddr, pNetIO);
498 // not found?
499 if (!pConn)
500 {
501 // allow connect?
502 if (!fAllowConnect) return false;
503 // create new connection object
504 uint32_t iConnID = iNextConnID++;
505 pConn = new C4Network2IOConnection();
506 pConn->Set(pnNetClass: pNetIO, eProt: getNetIOProt(pNetIO), nPeerAddr: PeerAddr, nConnectAddr: ConnectAddr, nStatus: CS_Connected, szPassword: nullptr, iID: iConnID);
507 // add to list
508 AddConnection(pConn);
509 }
510 else
511 {
512 // already closed this connection (attempt)?
513 if (pConn->isClosed())
514 return false;
515 if (!pConn->isOpen())
516 {
517 // change status
518 pConn->SetStatus(CS_Connected);
519 pConn->SetPeerAddr(PeerAddr);
520 }
521 }
522 // send welcome packet, if appropriate
523 SendConnPackets();
524#if (C4NET2IO_DUMP_LEVEL > 0)
525 // log
526 logger->debug("Network: got {} connection from {}", getNetIOName(pNetIO), PeerAddr.ToString());
527#endif
528 // ok
529 return true;
530}
531
532void C4Network2IO::OnDisconn(const C4NetIO::addr_t &addr, C4NetIO *pNetIO, const char *szReason)
533{
534 if (pNetIO == pNetIO_UDP && IsPuncherAddr(addr))
535 {
536 if (PuncherAddrIPv4 == addr)
537 PuncherAddrIPv4.Clear();
538 else
539 PuncherAddrIPv6.Clear();
540 return;
541 }
542#if (C4NET2IO_DUMP_LEVEL > 1)
543 unsigned int iTime = timeGetTime();
544 logger->debug("OnDisconn: {}:{:02}:{:02}:{:03}: {}",
545 (iTime / 1000 / 60 / 60), (iTime / 1000 / 60) % 60, (iTime / 1000) % 60, iTime % 1000,
546 getNetIOName(pNetIO));
547#endif
548 // find connection
549 C4Network2IOConnection *pConn = GetConnection(addr, pNetIO);
550 if (!pConn) pConn = GetConnectionByConnAddr(addr, pNetIO);
551 if (!pConn) return;
552 // log
553 logger->info(fmt: "{} connection to {} {} ({})",
554 args: getNetIOName(pNetIO), args: addr.ToString(), args: (pConn->isConnecting() ? "failed" : "closed"), args&: szReason);
555 // already closed? ignore
556 if (!pConn->isClosed())
557 // not accepted yet? count as connection failure
558 pConn->SetStatus(pConn->isHalfAccepted() ? CS_Closed : CS_ConnectFail);
559 // keep connection for main thread message
560 pConn->AddRef();
561 // check for pending welcome packets
562 SendConnPackets();
563 // signal to main thread
564 Application.InteractiveThread.PushEvent(eEventType: Ev_Net_Disconn, data: pConn);
565 // don't remove connection from list - wait for postmortem or timeout
566}
567
568void C4Network2IO::OnPacket(const class C4NetIOPacket &rPacket, C4NetIO *pNetIO)
569{
570#if (C4NET2IO_DUMP_LEVEL > 1)
571 unsigned int iTime = timeGetTime();
572 logger->debug("OnPacket: {}:{:02}:{:02}:{:03}: status {:02x} {}",
573 (iTime / 1000 / 60 / 60), (iTime / 1000 / 60) % 60, (iTime / 1000) % 60, iTime % 1000,
574 rPacket.getStatus(), getNetIOName(pNetIO));
575#endif
576 if (pNetIO == pNetIO_UDP && IsPuncherAddr(addr: rPacket.getAddr()))
577 {
578 HandlePuncherPacket(packet: rPacket);
579 return;
580 }
581 if (!rPacket.getSize()) return;
582 // find connection
583 C4Network2IOConnection *pConn = GetConnection(addr: rPacket.getAddr(), pNetIO);
584 if (!pConn)
585 {
586 logger->error(fmt: "could not find connection for {} packet (status {:02x}) from {}!", args: getNetIOName(pNetIO), args: rPacket.getStatus(), args: rPacket.getAddr().ToString());
587 return;
588 }
589#if (C4NET2IO_DUMP_LEVEL > 2)
590 if (timeGetTime() - iTime > 100)
591 logger->debug("OnPacket: ... blocked {} ms for finding the connection!", timeGetTime() - iTime);
592#endif
593 // notify
594 pConn->OnPacketReceived(iPacketType: rPacket.getStatus());
595 // handle packet
596 HandlePacket(rPacket, pConn, fThread: true);
597 // log time
598#if (C4NET2IO_DUMP_LEVEL > 1)
599 if (timeGetTime() - iTime > 100)
600 logger->debug("OnPacket: ... blocked {} ms for handling!", timeGetTime() - iTime);
601#endif
602}
603
604bool C4Network2IO::Execute(int iTimeout)
605{
606 iLastExecute = timeGetTime();
607
608 // check for timeout
609 CheckTimeout();
610
611 // ping all open connections
612 if (!Inside<long unsigned int>(ival: iLastPing, lbound: timeGetTime() - C4NetPingFreq, rbound: timeGetTime()))
613 {
614 Ping();
615 iLastPing = iLastExecute;
616 }
617
618 // do statistics
619 if (!Inside<long unsigned int>(ival: iLastStatistic, lbound: timeGetTime() - C4NetStatisticsFreq, rbound: timeGetTime()))
620 {
621 GenerateStatistics(iInterval: iLastExecute - iLastStatistic);
622 iLastStatistic = iLastExecute;
623 }
624
625 // ressources
626 Game.Network.ResList.OnTimer();
627
628 // ok
629 return true;
630}
631
632int C4Network2IO::GetTimeout()
633{
634 return std::max<int>(a: 0, b: iLastExecute + C4NetTimer - timeGetTime());
635}
636
637void C4Network2IO::OnThreadEvent(C4InteractiveEventType eEvent, const std::any &eventData) // by main thread
638{
639 switch (eEvent)
640 {
641 case Ev_Net_Conn: // got a connection
642 {
643 C4Network2IOConnection *pConn = std::any_cast<C4Network2IOConnection *>(any: eventData);
644 // do callback
645 Game.Network.OnConn(pConn);
646 // remove reference
647 pConn->DelRef();
648 }
649 break;
650
651 case Ev_Net_Disconn: // connection closed
652 {
653 C4Network2IOConnection *pConn = std::any_cast<C4Network2IOConnection *>(any: eventData);
654 assert(pConn->isClosed());
655 // do callback
656 Game.Network.OnDisconn(pConn);
657 // remove reference
658 pConn->DelRef();
659 }
660 break;
661
662 case Ev_Net_Packet: // got packet
663 {
664 NetEvPacketData *pData = std::any_cast<NetEvPacketData *>(any: eventData);
665 // handle
666 HandlePacket(rPacket: pData->Packet, pConn: pData->Conn, fThread: false);
667 // clear up
668 pData->Conn->DelRef();
669 delete pData;
670 }
671 break;
672
673 default:
674 // not in our interest
675 break;
676 }
677}
678
679C4NetIO *C4Network2IO::getNetIO(C4Network2IOProtocol eProt) // by both
680{
681 switch (eProt)
682 {
683 case P_UDP: return pNetIO_UDP;
684 case P_TCP: return pNetIO_TCP;
685 case P_NONE:
686 assert(!"C4Network2IOProtocol P_NONE");
687 return nullptr;
688 }
689 return nullptr;
690}
691
692const char *C4Network2IO::getNetIOName(C4NetIO *pNetIO)
693{
694 if (!pNetIO) return "nullptr";
695 if (pNetIO == pNetIO_TCP) return "TCP";
696 if (pNetIO == pNetIO_UDP) return "UDP";
697 return "UNKNOWN";
698}
699
700C4Network2IOProtocol C4Network2IO::getNetIOProt(C4NetIO *pNetIO)
701{
702 if (!pNetIO) return P_NONE;
703 if (pNetIO == pNetIO_TCP) return P_TCP;
704 if (pNetIO == pNetIO_UDP) return P_UDP;
705 return P_NONE;
706}
707
708void C4Network2IO::AddConnection(C4Network2IOConnection *pConn) // by both
709{
710 CStdLock ConnListLock(&ConnListCSec);
711 // add reference
712 pConn->AddRef();
713 // add to list
714 pConn->pNext = pConnList; pConnList = pConn;
715}
716
717void C4Network2IO::RemoveConnection(C4Network2IOConnection *pConn) // by both
718{
719 CStdLock ConnListLock(&ConnListCSec);
720 // search & remove
721 if (pConnList == pConn)
722 pConnList = pConn->pNext;
723 else
724 {
725 C4Network2IOConnection *pAct;
726 for (pAct = pConnList; pAct; pAct = pAct->pNext)
727 if (pAct->pNext == pConn)
728 break;
729 if (pAct)
730 pAct->pNext = pConn->pNext;
731 else
732 return;
733 }
734 // remove reference
735 pConn->pNext = nullptr; pConn->DelRef();
736}
737
738C4Network2IOConnection *C4Network2IO::GetConnection(const C4NetIO::addr_t &addr, C4NetIO *pNetIO) // by both
739{
740 CStdLock ConnListLock(&ConnListCSec);
741 // search
742 for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext)
743 if (pConn->getNetClass() == pNetIO && pConn->getPeerAddr() == addr)
744 return pConn;
745 return nullptr;
746}
747
748C4Network2IOConnection *C4Network2IO::GetConnectionByConnAddr(const C4NetIO::addr_t &addr, C4NetIO *pNetIO) // by both
749{
750 CStdLock ConnListLock(&ConnListCSec);
751 // search
752 for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext)
753 if (pConn->getNetClass() == pNetIO && pConn->getConnectAddr() == addr)
754 return pConn;
755 return nullptr;
756}
757
758C4Network2IOConnection *C4Network2IO::GetConnectionByID(uint32_t iConnID) // by thread
759{
760 CStdLock ConnListLock(&ConnListCSec);
761 // search
762 for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext)
763 if (pConn->getID() == iConnID)
764 return pConn;
765 return nullptr;
766}
767
768void C4Network2IO::SetReference(C4Network2Reference *pReference)
769{
770 if (pRefServer)
771 pRefServer->SetReference(pReference);
772 else
773 delete pReference;
774}
775
776bool C4Network2IO::IsReferenceNeeded()
777{
778 return !!pRefServer;
779}
780
781bool C4Network2IO::doAutoAccept(const C4ClientCore &CCore, const C4Network2IOConnection &Conn)
782{
783 CStdLock AALock(&AutoAcceptCSec);
784 // check if connection with the given client should be allowed
785 for (AutoAccept *pAcc = pAutoAcceptList; pAcc; pAcc = pAcc->Next)
786 // core match?
787 if (CCore.getDiffLevel(CCore2: pAcc->CCore) <= C4ClientCoreDL_IDMatch)
788 {
789 // check: already got another connection for this client? Peer IP must match, then.
790 for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext)
791 if (pConn->isAccepted() &&
792 pConn->getCCore().getDiffLevel(CCore2: CCore) <= C4ClientCoreDL_IDMatch &&
793 pConn->getPeerAddr().GetHost() != Conn.getPeerAddr().GetHost())
794 return false;
795 // not found or IP matches? Let pass
796 return true;
797 }
798 return false;
799}
800
801bool C4Network2IO::HandlePacket(const C4NetIOPacket &rPacket, C4Network2IOConnection *pConn, bool fThread)
802{
803 // security: add connection reference
804 if (!pConn) return false; pConn->AddRef();
805
806 // accept only PID_Conn and PID_Ping on non-accepted connections
807 if (!pConn->isHalfAccepted())
808 if (rPacket.getStatus() != PID_Conn && rPacket.getStatus() != PID_Ping && rPacket.getStatus() != PID_ConnRe)
809 {
810 pConn->DelRef();
811 return false;
812 }
813
814 // unpack packet (yet another no-idea-why-it's-needed-cast)
815 C4IDPacket Pkt; C4PacketBase &PktB = Pkt;
816 try
817 {
818 PktB.unpack(Pkt: rPacket);
819 }
820 catch (const StdCompiler::Exception &e)
821 {
822 logger->error(fmt: "Failed to unpack packet id {:02x}: {}", args: rPacket.getStatus(), args: e.what());
823#ifdef NDEBUG
824 pConn->Close();
825#endif
826 return false;
827 }
828
829 // dump packet (network thread only)
830#if (C4NET2IO_DUMP_LEVEL > 0)
831 if (fThread && Pkt.getPktType() != PID_Ping && Pkt.getPktType() != PID_Pong && Pkt.getPktType() != PID_NetResData)
832 {
833 unsigned int iTime = timeGetTime();
834 const std::string packetHeader{std::format("HandlePacket: {}:{:02}:{:02}:{:03} by {} ({} bytes, counter {})",
835 (iTime / 1000 / 60 / 60), (iTime / 1000 / 60) % 60, (iTime / 1000) % 60, iTime % 1000,
836 pConn->getPeerAddr().ToString(),
837 rPacket.getSize(), pConn->getInPacketCounter())};
838 const std::string dump{DecompileToBuf<StdCompilerINIWrite>(mkNamingAdapt(Pkt, packetHeader.c_str()))};
839 logger->debug(dump);
840 }
841#endif
842
843 // search packet handling data
844 bool fSendToMainThread = false, fHandled = false;
845 for (const C4PktHandlingData *pHData = PktHandlingData; pHData->ID != PID_None; pHData++)
846 if (pHData->ID == rPacket.getStatus())
847 // correct thread?
848 if (!pHData->ProcByThread == !fThread)
849 {
850 // connection accepted?
851 if (pHData->AcceptedOnly || pConn->isAccepted() || pConn->isClosed())
852 {
853 fHandled = true;
854#if (C4NET2IO_DUMP_LEVEL > 2)
855 unsigned int iStart = timeGetTime();
856#endif
857
858 // call handler(s)
859 CallHandlers(iHandlers: pHData->HandlerID, pPacket: &Pkt, pConn, fThread);
860
861#if (C4NET2IO_DUMP_LEVEL > 2)
862 if (fThread && timeGetTime() - iStart > 100)
863 logger->debug("HandlePacket: ... blocked for {} ms!", timeGetTime() - iStart);
864#endif
865 }
866 }
867 // transfer to main thread?
868 else if (!pHData->ProcByThread && fThread)
869 {
870 fHandled = true;
871 fSendToMainThread = true;
872 }
873
874 // send to main thread?
875 if (fSendToMainThread)
876 {
877 // create data
878 NetEvPacketData *pEvData = new NetEvPacketData;
879 pEvData->Packet.Take(Buf2: rPacket.Duplicate());
880 pEvData->Conn = pConn; pConn->AddRef();
881 // trigger event
882 if (!Application.InteractiveThread.PushEvent(eEventType: Ev_Net_Packet, data: pEvData))
883 logger->error(msg: "...push event ");
884 }
885
886 // unhandled?
887 if (!fHandled && !pConn->isClosed())
888 logger->error(fmt: "Unhandled packet (status {:02x})", args: rPacket.getStatus());
889
890 // remove connection reference
891 pConn->DelRef();
892 return fHandled;
893}
894
895void C4Network2IO::CallHandlers(int iHandlerID, const C4IDPacket *pPkt, C4Network2IOConnection *pConn, bool fThread)
896{
897 // emulate old callbacks
898 char cStatus = pPkt->getPktType();
899 const C4PacketBase *pPacket = pPkt->getPkt();
900 // this class (network thread)
901 if (iHandlerID & PH_C4Network2IO)
902 {
903 assert(fThread);
904 HandlePacket(cStatus, pPacket, pConn);
905 }
906 // main network class (main thread)
907 if (iHandlerID & PH_C4Network2)
908 {
909 assert(!fThread);
910 Game.Network.HandlePacket(cStatus, pBasePkt: pPacket, pConn);
911 }
912 // fullscreen lobby
913 if (iHandlerID & PH_C4GUIMainDlg)
914 {
915 assert(!fThread);
916 Game.Network.HandleLobbyPacket(cStatus, pBasePkt: pPacket, pConn);
917 }
918 // client list class (main thread)
919 if (iHandlerID & PH_C4Network2ClientList)
920 {
921 assert(!fThread);
922 Game.Network.Clients.HandlePacket(cStatus, pBasePkt: pPacket, pConn);
923 }
924 // player list class (main thread)
925 if (iHandlerID & PH_C4Network2Players)
926 {
927 assert(!fThread);
928 Game.Network.Players.HandlePacket(cStatus, pPacket, pConn);
929 }
930 // ressource list class (network thread)
931 if (iHandlerID & PH_C4Network2ResList)
932 {
933 assert(fThread);
934 Game.Network.ResList.HandlePacket(cStatus, pPacket, pConn);
935 }
936 // network control (mixed)
937 if (iHandlerID & PH_C4GameControlNetwork)
938 {
939 Game.Control.Network.HandlePacket(cStatus, pPacket, pConn);
940 }
941}
942
943void C4Network2IO::HandlePacket(char cStatus, const C4PacketBase *pPacket, C4Network2IOConnection *pConn)
944{
945 // security
946 if (!pConn) return;
947
948#define GETPKT(type, name) \
949 assert(pPacket); \
950 const type &name = static_cast<const type &>(*pPacket);
951
952 switch (cStatus)
953 {
954 case PID_Conn: // connection request
955 {
956 if (!pConn->isOpen()) break;
957 // get packet
958 GETPKT(C4PacketConn, rPkt);
959 // set connection ID
960 pConn->SetRemoteID(rPkt.getConnID());
961 // check auto-accept
962 if (doAutoAccept(CCore: rPkt.getCCore(), Conn: *pConn))
963 {
964 // send answer back
965 C4PacketConnRe pcr(true, false, "auto accept");
966 if (!pConn->Send(rPkt: MkC4NetIOPacket(cStatus: PID_ConnRe, Pkt: pcr)))
967 pConn->Close();
968 // accept
969 pConn->SetStatus(CS_HalfAccepted);
970 pConn->SetCCore(rPkt.getCCore());
971 pConn->SetAutoAccepted();
972 }
973 // note that this packet will get processed by C4Network2, too (main thread)
974 }
975 break;
976
977 case PID_ConnRe: // connection request reply
978 {
979 if (!pConn->isOpen()) break;
980 // conn not sent? That's fishy.
981 // FIXME: Note this happens if the peer has exclusive connection mode on.
982 if (!pConn->isConnSent())
983 {
984 pConn->Close();
985 break;
986 }
987 // get packet
988 GETPKT(C4PacketConnRe, rPkt);
989 // auto accept connection
990 if (rPkt.isOK())
991 {
992 if (pConn->isHalfAccepted() && pConn->isAutoAccepted())
993 pConn->SetAccepted();
994 }
995 }
996 break;
997
998 case PID_Ping:
999 {
1000 if (!pConn->isOpen()) break;
1001 GETPKT(C4PacketPing, rPkt);
1002 // pong
1003 C4PacketPing PktPong = rPkt;
1004 pConn->Send(rPkt: MkC4NetIOPacket(cStatus: PID_Pong, Pkt: PktPong));
1005 // remove received packets from log
1006 pConn->ClearPacketLog(iStartNumber: rPkt.getPacketCounter());
1007 }
1008 break;
1009
1010 case PID_Pong:
1011 {
1012 if (!pConn->isOpen()) break;
1013 GETPKT(C4PacketPing, rPkt);
1014 // save
1015 pConn->SetPingTime(rPkt.getTravelTime());
1016 }
1017 break;
1018
1019 case PID_FwdReq:
1020 {
1021 GETPKT(C4PacketFwd, rPkt);
1022 HandleFwdReq(rFwd: rPkt, pBy: pConn);
1023 }
1024 break;
1025
1026 case PID_Fwd:
1027 {
1028 GETPKT(C4PacketFwd, rPkt);
1029 // only received accidently?
1030 if (!rPkt.DoFwdTo(iClient: LCCore.getID())) break;
1031 // handle
1032 HandlePacket(rPacket: rPkt.getData(), pConn, fThread: true);
1033 }
1034 break;
1035
1036 case PID_PostMortem:
1037 {
1038 GETPKT(C4PacketPostMortem, rPkt);
1039 // Get connection
1040 C4Network2IOConnection *pConn = GetConnectionByID(iConnID: rPkt.getConnID());
1041 if (!pConn) return;
1042 // Handle all packets
1043 uint32_t iCounter;
1044 for (iCounter = pConn->getInPacketCounter(); ; iCounter++)
1045 {
1046 // Get packet
1047 const C4NetIOPacket *pPkt = rPkt.getPacket(iNumber: iCounter);
1048 if (!pPkt) break;
1049 // Handle it
1050 HandlePacket(rPacket: *pPkt, pConn, fThread: true);
1051 }
1052 // Log
1053 if (iCounter > pConn->getInPacketCounter())
1054 logger->info(fmt: "Recovered {} packets", args: iCounter - pConn->getInPacketCounter());
1055 // Remove the connection from our list
1056 if (!pConn->isClosed())
1057 pConn->Close();
1058 RemoveConnection(pConn);
1059 }
1060 break;
1061 }
1062
1063#undef GETPKT
1064}
1065
1066void C4Network2IO::HandleFwdReq(const C4PacketFwd &rFwd, C4Network2IOConnection *pBy)
1067{
1068 CStdLock ConnListLock(&ConnListCSec);
1069 // init packet
1070 C4PacketFwd nFwd;
1071 nFwd.SetListType(false);
1072 // find all clients the message should be forwarded to
1073 int iClientID; C4Network2IOConnection *pConn;
1074 for (pConn = pConnList; pConn; pConn = pConn->pNext)
1075 if (pConn->isAccepted())
1076 if ((iClientID = pConn->getClientID()) >= 0)
1077 if (iClientID != pBy->getClientID())
1078 if (rFwd.DoFwdTo(iClient: iClientID) && !nFwd.DoFwdTo(iClient: iClientID))
1079 nFwd.AddClient(iClient: iClientID);
1080 // check count (hardcoded: broadcast for > 2 clients)
1081 if (nFwd.getClientCnt() <= 2)
1082 {
1083 C4NetIOPacket Tmp = rFwd.getData();
1084 C4NetIOPacket Pkt = Tmp.getRef();
1085 for (int i = 0; i < nFwd.getClientCnt(); i++)
1086 if (pConn = GetMsgConnection(iClientID: nFwd.getClient(i)))
1087 {
1088 pConn->Send(rPkt: Pkt);
1089 pConn->DelRef();
1090 }
1091 }
1092 else
1093 {
1094 // Temporarily unlock connection list for getting broadcast lock
1095 // (might lead to deathlocks otherwise, as the lock is often taken
1096 // in the opposite order)
1097 ConnListLock.Clear();
1098
1099 BeginBroadcast();
1100 nFwd.SetData(rFwd.getData());
1101 // add all clients
1102 CStdLock ConnListLock(&ConnListCSec);
1103 for (int i = 0; i < nFwd.getClientCnt(); i++)
1104 if (pConn = GetMsgConnection(iClientID: nFwd.getClient(i)))
1105 {
1106 pConn->SetBroadcastTarget(true);
1107 pConn->DelRef();
1108 }
1109 // broadcast
1110 Broadcast(rPkt: MkC4NetIOPacket(cStatus: PID_Fwd, Pkt: nFwd));
1111 EndBroadcast();
1112 }
1113 // doing a callback here; don't lock!
1114 ConnListLock.Clear();
1115 // forward to self?
1116 if (rFwd.DoFwdTo(iClient: LCCore.getID()))
1117 HandlePacket(rPacket: rFwd.getData(), pConn: pBy, fThread: true);
1118}
1119
1120void C4Network2IO::HandlePuncherPacket(const C4NetIOPacket &packet)
1121{
1122 auto pkt = C4NetpuncherPacket::Construct(pkt: packet);
1123 if (!pkt || !Game.Network.HandlePuncherPacket(std::move(pkt), family: packet.getAddr().GetFamily()))
1124 {
1125 assert(pNetIO_UDP);
1126 pNetIO_UDP->Close(addr: packet.getAddr());
1127 }
1128}
1129
1130bool C4Network2IO::Ping()
1131{
1132 bool fSuccess = true;
1133 // ping all connections
1134 for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext)
1135 if (pConn->isOpen())
1136 {
1137 C4PacketPing Ping(pConn->getInPacketCounter());
1138 fSuccess &= pConn->Send(rPkt: MkC4NetIOPacket(cStatus: PID_Ping, Pkt: Ping));
1139 pConn->OnPing();
1140 }
1141 return fSuccess;
1142}
1143
1144void C4Network2IO::CheckTimeout()
1145{
1146 // acquire lock
1147 CStdLock ConnListLock(&ConnListCSec);
1148 // check all connections for timeout (use deletion-safe iteration method just in case)
1149 for (C4Network2IOConnection *pConn = pConnList, *pNext; pConn; pConn = pNext)
1150 {
1151 pNext = pConn->pNext;
1152 // status timeout
1153 if (!pConn->isClosed() && !pConn->isAccepted())
1154 if (difftime(time1: time(timer: nullptr), time0: pConn->getTimestamp()) > C4NetAcceptTimeout)
1155 {
1156 logger->info(fmt: "connection accept timeout to {}", args: pConn->getPeerAddr().ToString());
1157 pConn->Close();
1158 }
1159 // ping timeout
1160 if (pConn->isAccepted())
1161 if ((pConn->getLag() != -1 ? pConn->getLag() : 1000 * (time(timer: nullptr) - pConn->getTimestamp()))
1162 > C4NetPingTimeout)
1163 {
1164 logger->info(fmt: "ping timeout to {}", args: pConn->getPeerAddr().ToString());
1165 pConn->Close();
1166 }
1167 // delayed connection removal
1168 if (pConn->isClosed())
1169 if (difftime(time1: time(timer: nullptr), time0: pConn->getTimestamp()) > C4NetAcceptTimeout)
1170 RemoveConnection(pConn);
1171 }
1172}
1173
1174void C4Network2IO::GenerateStatistics(int iInterval)
1175{
1176 int iTCPIRateSum = 0, iTCPORateSum = 0,
1177 iUDPIRateSum = 0, iUDPORateSum = 0;
1178
1179 // acquire lock, get connection statistics
1180 CStdLock ConnListLock(&ConnListCSec);
1181 for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext)
1182 if (pConn->isOpen())
1183 {
1184 bool fTCP = pConn->getNetClass() == pNetIO_TCP;
1185 pConn->DoStatistics(iInterval, pIRateSum: fTCP ? &iTCPIRateSum : &iUDPIRateSum,
1186 pORateSum: fTCP ? &iTCPORateSum : &iUDPORateSum);
1187 }
1188 ConnListLock.Clear();
1189
1190 // get broadcast statistics
1191 int inTCPBCRate = 0, inUDPBCRate = 0;
1192 if (pNetIO_TCP) pNetIO_TCP->GetStatistic(pBroadcastRate: &inTCPBCRate);
1193 if (pNetIO_UDP) pNetIO_UDP->GetStatistic(pBroadcastRate: &inUDPBCRate);
1194
1195 // normalize everything
1196 iTCPIRateSum = iTCPIRateSum * 1000 / iInterval;
1197 iTCPORateSum = iTCPORateSum * 1000 / iInterval;
1198 iUDPIRateSum = iUDPIRateSum * 1000 / iInterval;
1199 iUDPORateSum = iUDPORateSum * 1000 / iInterval;
1200 inTCPBCRate = inTCPBCRate * 1000 / iInterval;
1201 inUDPBCRate = inUDPBCRate * 1000 / iInterval;
1202
1203 // clear
1204 if (pNetIO_TCP) pNetIO_TCP->ClearStatistic();
1205 if (pNetIO_UDP) pNetIO_UDP->ClearStatistic();
1206
1207 // save back
1208 iTCPIRate = iTCPIRateSum; iTCPORate = iTCPORateSum; iTCPBCRate = inTCPBCRate;
1209 iUDPIRate = iUDPIRateSum; iUDPORate = iUDPORateSum; iUDPBCRate = inUDPBCRate;
1210}
1211
1212void C4Network2IO::SendConnPackets()
1213{
1214 CStdLock ConnListLock(&ConnListCSec);
1215
1216 // exlusive conn?
1217 if (fExclusiveConn)
1218 // find a live connection
1219 for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext)
1220 if (pConn->isAccepted() || (!pConn->isClosed() && pConn->isConnSent()))
1221 // do not sent additional conn packets - no other connection should succeed
1222 return;
1223
1224 // sent pending welcome packet(s)
1225 for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext)
1226 if (pConn->isOpen() && !pConn->isConnSent())
1227 {
1228 // make packet
1229 CStdLock LCCoreLock(&LCCoreCSec);
1230 C4NetIOPacket Pkt = MkC4NetIOPacket(cStatus: PID_Conn, Pkt: C4PacketConn(LCCore, pConn->getID(), pConn->getPassword()));
1231 LCCoreLock.Clear();
1232 // send
1233 if (!pConn->Send(rPkt: Pkt))
1234 pConn->Close();
1235 else
1236 {
1237 // set flag
1238 pConn->SetConnSent();
1239 // only one conn packet at a time
1240 if (fExclusiveConn)
1241 return;
1242 }
1243 }
1244}
1245
1246// *** C4Network2IOConnection
1247
1248C4Network2IOConnection::C4Network2IOConnection()
1249 : pNetClass(nullptr),
1250 iID(~0), iRemoteID(~0),
1251 fAutoAccept(false),
1252 fBroadcastTarget(false),
1253 iTimestamp(0),
1254 iPingTime(-1),
1255 iLastPing(~0), iLastPong(~0),
1256 iOutPacketCounter(0), iInPacketCounter(0),
1257 pPacketLog(nullptr),
1258 pNext(nullptr),
1259 iRefCnt(0),
1260 fConnSent(false),
1261 fPostMortemSent(false) {}
1262
1263C4Network2IOConnection::~C4Network2IOConnection()
1264{
1265 assert(!iRefCnt);
1266 // connection needs to be closed?
1267 if (pNetClass && !isClosed()) Close();
1268 // clear the packet log
1269 ClearPacketLog();
1270}
1271
1272int C4Network2IOConnection::getLag() const
1273{
1274 // Last ping not answered yet?
1275 if (iPingTime != -1 && iLastPing != ~0 && (iLastPong == ~0 || iLastPing > iLastPong))
1276 {
1277 int iPingLag = timeGetTime() - iLastPing;
1278 // Use it for lag measurement once it's larger then the last ping time
1279 // (the ping time won't be better than this anyway once the pong's here)
1280 return (std::max)(a: iPingLag, b: iPingTime);
1281 }
1282 // Last ping result
1283 return iPingTime;
1284}
1285
1286void C4Network2IOConnection::Set(C4NetIO *pnNetClass, C4Network2IOProtocol enProt, const C4NetIO::addr_t &nPeerAddr, const C4NetIO::addr_t &nConnectAddr, C4Network2IOConnStatus nStatus, const char *szPassword, uint32_t inID)
1287{
1288 // save data
1289 pNetClass = pnNetClass; eProt = enProt;
1290 PeerAddr = nPeerAddr; ConnectAddr = nConnectAddr;
1291 Status = nStatus;
1292 Password = szPassword;
1293 iID = inID;
1294 // initialize
1295 fBroadcastTarget = false;
1296 iTimestamp = time(timer: nullptr); iPingTime = -1;
1297}
1298
1299void C4Network2IOConnection::SetSocket(std::unique_ptr<C4NetIOTCP::Socket> socket)
1300{
1301 tcpSimOpenSocket = std::move(socket);
1302}
1303
1304void C4Network2IOConnection::SetRemoteID(uint32_t inRemoteID)
1305{
1306 iRemoteID = inRemoteID;
1307}
1308
1309void C4Network2IOConnection::SetPeerAddr(const C4NetIO::addr_t &nPeerAddr)
1310{
1311 // just do it
1312 PeerAddr = nPeerAddr;
1313}
1314
1315void C4Network2IOConnection::OnPing()
1316{
1317 // Still no pong for the last ping?
1318 if (iLastPong < iLastPing)
1319 return;
1320 // Save time
1321 iLastPing = timeGetTime();
1322}
1323
1324void C4Network2IOConnection::SetPingTime(int inPingTime)
1325{
1326 // save it
1327 iPingTime = inPingTime;
1328 // pong received - save timestamp
1329 iLastPong = timeGetTime();
1330}
1331
1332void C4Network2IOConnection::SetStatus(C4Network2IOConnStatus nStatus)
1333{
1334 if (nStatus != Status)
1335 {
1336 // Connection can't return from these
1337 assert(!isClosed());
1338 // set status
1339 Status = nStatus;
1340 // reset timestamp for connect/accept/close
1341 if (Status == CS_Connect || Status == CS_Connected || Status == CS_Accepted || Status == CS_Closed)
1342 iTimestamp = time(timer: nullptr);
1343 }
1344}
1345
1346void C4Network2IOConnection::SetAutoAccepted()
1347{
1348 fAutoAccept = true;
1349}
1350
1351void C4Network2IOConnection::OnPacketReceived(uint8_t iPacketType)
1352{
1353 // Just count them
1354 if (iPacketType >= PID_PacketLogStart)
1355 iInPacketCounter++;
1356}
1357
1358void C4Network2IOConnection::ClearPacketLog(uint32_t iUntilID)
1359{
1360 // Search position of first packet to delete
1361 PacketLogEntry *pPos, *pPrev = nullptr;
1362 for (pPos = pPacketLog; pPos; pPrev = pPos, pPos = pPos->Next)
1363 if (pPos->Number < iUntilID)
1364 break;
1365 if (pPos)
1366 {
1367 // Remove packets from list
1368 (pPrev ? pPrev->Next : pPacketLog) = nullptr;
1369 // Delete everything
1370 while (pPos)
1371 {
1372 PacketLogEntry *pDelete = pPos;
1373 pPos = pPos->Next;
1374 delete pDelete;
1375 }
1376 }
1377}
1378
1379bool C4Network2IOConnection::CreatePostMortem(C4PacketPostMortem *pPkt)
1380{
1381 // Security
1382 if (!pPkt) return false;
1383 CStdLock PacketLogLock(&PacketLogCSec);
1384 // Nothing to do?
1385 if (!pPacketLog) return false;
1386 // Already created?
1387 if (fPostMortemSent) return false;
1388 // Set connection ID and packet counter
1389 pPkt->SetConnID(iRemoteID);
1390 pPkt->SetPacketCounter(iOutPacketCounter);
1391 // Add packets
1392 for (PacketLogEntry *pEntry = pPacketLog; pEntry; pEntry = pEntry->Next)
1393 pPkt->Add(rPkt: pEntry->Pkt);
1394 // Okay
1395 fPostMortemSent = true;
1396 return true;
1397}
1398
1399void C4Network2IOConnection::SetCCore(const C4ClientCore &nCCore)
1400{
1401 CStdLock CCoreLock(&CCoreCSec);
1402 CCore = nCCore;
1403}
1404
1405bool C4Network2IOConnection::Connect()
1406{
1407 if (!pNetClass) return false;
1408 if (tcpSimOpenSocket)
1409 {
1410 const auto netTcp = dynamic_cast<C4NetIOTCP *>(pNetClass);
1411 return netTcp->Connect(addr: ConnectAddr, socket: std::move(tcpSimOpenSocket));
1412 }
1413 // try connect
1414 return pNetClass->Connect(addr: ConnectAddr);
1415}
1416
1417void C4Network2IOConnection::Close()
1418{
1419 if (!pNetClass || isClosed()) return;
1420 // set status
1421 SetStatus(CS_Closed);
1422 // close
1423 pNetClass->Close(addr: PeerAddr);
1424}
1425
1426bool C4Network2IOConnection::Send(const C4NetIOPacket &rPkt)
1427{
1428 // some packets shouldn't go into the log
1429 if (rPkt.getStatus() < PID_PacketLogStart)
1430 {
1431 assert(isOpen());
1432 C4NetIOPacket Copy(rPkt);
1433 Copy.SetAddr(PeerAddr);
1434 return pNetClass->Send(rPacket: Copy);
1435 }
1436 CStdLock PacketLogLock(&PacketLogCSec);
1437 // create log entry
1438 PacketLogEntry *pLogEntry = new PacketLogEntry();
1439 pLogEntry->Number = iOutPacketCounter++;
1440 pLogEntry->Pkt = rPkt;
1441 pLogEntry->Next = pPacketLog;
1442 pPacketLog = pLogEntry;
1443 // set address
1444 pLogEntry->Pkt.SetAddr(PeerAddr);
1445 // closed? No sweat, post mortem will reroute it later.
1446 if (!isOpen())
1447 {
1448 // post mortem already sent? This shouldn't happen
1449 if (fPostMortemSent) { assert(false); return false; }
1450 // okay then
1451 return true;
1452 }
1453 // send
1454 bool fSuccess = pNetClass->Send(rPacket: pLogEntry->Pkt);
1455 if (fSuccess)
1456 assert(!fPostMortemSent);
1457 return fSuccess;
1458}
1459
1460void C4Network2IOConnection::SetBroadcastTarget(bool fSet)
1461{
1462 // Note that each thread will have to make sure that this flag won't be
1463 // changed until Broadcast() is called. See C4Network2IO::BroadcastCSec.
1464 pNetClass->SetBroadcast(addr: PeerAddr, fSet);
1465 fBroadcastTarget = fSet;
1466}
1467
1468void C4Network2IOConnection::DoStatistics(int iInterval, int *pIRateSum, int *pORateSum)
1469{
1470 // get C4NetIO statistics
1471 int inIRate, inORate, inLoss;
1472 if (!isOpen() || !pNetClass->GetConnStatistic(addr: PeerAddr, pIRate: &inIRate, pORate: &inORate, pLoss: &inLoss))
1473 {
1474 iIRate = iORate = iPacketLoss = 0;
1475 return;
1476 }
1477 // normalize
1478 inIRate = inIRate * 1000 / iInterval;
1479 inORate = inORate * 1000 / iInterval;
1480 // set
1481 iIRate = inIRate; iORate = inORate; iPacketLoss = inLoss;
1482 // sum up
1483 if (pIRateSum) *pIRateSum += iIRate;
1484 if (pORateSum) *pORateSum += iORate;
1485}
1486
1487void C4Network2IOConnection::AddRef()
1488{
1489 ++iRefCnt;
1490}
1491
1492void C4Network2IOConnection::DelRef()
1493{
1494 if (--iRefCnt == 0) delete this;
1495}
1496
1497// *** C4PacketPostMortem
1498
1499C4PacketPostMortem::C4PacketPostMortem()
1500 : iConnID(~0),
1501 iPacketCounter(~0),
1502 iPacketCount(0),
1503 pPackets(nullptr) {}
1504
1505C4PacketPostMortem::~C4PacketPostMortem()
1506{
1507 while (pPackets)
1508 {
1509 PacketLink *pDelete = pPackets;
1510 pPackets = pPackets->Next;
1511 delete pDelete;
1512 }
1513 iPacketCount = 0;
1514}
1515
1516const C4NetIOPacket *C4PacketPostMortem::getPacket(uint32_t iNumber) const
1517{
1518 // Security
1519 if (!Inside(ival: iNumber, lbound: iPacketCounter - iPacketCount, rbound: iPacketCounter - 1))
1520 return nullptr;
1521 // Calculate position in list
1522 iNumber = iNumber + iPacketCount - iPacketCounter;
1523 // Search for the packet with the given number
1524 PacketLink *pLink = pPackets;
1525 for (; pLink && iNumber; iNumber--)
1526 pLink = pLink->Next;
1527 // Not found?
1528 return pLink ? &pLink->Pkt : nullptr;
1529}
1530
1531void C4PacketPostMortem::SetPacketCounter(uint32_t inPacketCounter)
1532{
1533 iPacketCounter = inPacketCounter;
1534}
1535
1536void C4PacketPostMortem::Add(const C4NetIOPacket &rPkt)
1537{
1538 // Add to head of list (reverse order)
1539 PacketLink *pLink = new PacketLink();
1540 pLink->Pkt = rPkt;
1541 pLink->Next = pPackets;
1542 pPackets = pLink;
1543 iPacketCount++;
1544}
1545
1546void C4PacketPostMortem::CompileFunc(StdCompiler *pComp)
1547{
1548 bool fCompiler = pComp->isCompiler();
1549
1550 // Connection ID, packet number and packet count
1551 pComp->Value(rStruct: mkNamingAdapt(rValue&: iConnID, szName: "ConnID"));
1552 pComp->Value(rStruct: mkNamingAdapt(rValue&: iPacketCounter, szName: "PacketCounter"));
1553 pComp->Value(rStruct: mkNamingAdapt(rValue&: iPacketCount, szName: "PacketCount"));
1554
1555 // Packets
1556 if (fCompiler)
1557 {
1558 // Read packets
1559 for (uint32_t i = 0; i < iPacketCount; i++)
1560 {
1561 // Create list entry
1562 PacketLink *pLink = new PacketLink();
1563 pLink->Next = pPackets;
1564 pPackets = pLink;
1565 // Compile data
1566 pComp->Value(rStruct: mkNamingAdapt(rValue&: pLink->Pkt, szName: "PacketData"));
1567 }
1568 // Reverse order
1569 PacketLink *pPackets2 = pPackets;
1570 pPackets = nullptr;
1571 while (pPackets2)
1572 {
1573 // Get link
1574 PacketLink *pLink = pPackets2;
1575 pPackets2 = pLink->Next;
1576 // Readd to list
1577 pLink->Next = pPackets;
1578 pPackets = pLink;
1579 }
1580 }
1581 else
1582 {
1583 // Write packets
1584 for (PacketLink *pLink = pPackets; pLink; pLink = pLink->Next)
1585 pComp->Value(rStruct: mkNamingAdapt(rValue&: pLink->Pkt, szName: "PacketData"));
1586 }
1587}
1588
1589// *** C4PacketConn
1590
1591C4PacketConn::C4PacketConn()
1592 : iVer(C4XVERBUILD) {}
1593
1594C4PacketConn::C4PacketConn(const C4ClientCore &nCCore, uint32_t inConnID, const char *szPassword)
1595 : iVer(C4XVERBUILD),
1596 iConnID(inConnID),
1597 CCore(nCCore),
1598 Password(szPassword) {}
1599
1600void C4PacketConn::CompileFunc(StdCompiler *pComp)
1601{
1602 pComp->Value(rStruct: mkNamingAdapt(rValue&: CCore, szName: "CCore"));
1603 pComp->Value(rStruct: mkNamingAdapt(rValue: mkIntPackAdapt(rVal&: iVer), szName: "Version", rDefault: -1));
1604 pComp->Value(rStruct: mkNamingAdapt(rValue&: Password, szName: "Password", rDefault: ""));
1605 pComp->Value(rStruct: mkNamingAdapt(rValue: mkIntPackAdapt(rVal&: iConnID), szName: "ConnID", rDefault: ~0u));
1606}
1607
1608// *** C4PacketConnRe
1609
1610C4PacketConnRe::C4PacketConnRe() {}
1611
1612C4PacketConnRe::C4PacketConnRe(bool fnOK, bool fWrongPassword, const char *sznMsg)
1613 : fOK(fnOK),
1614 fWrongPassword(fWrongPassword),
1615 szMsg(sznMsg, true) {}
1616
1617void C4PacketConnRe::CompileFunc(StdCompiler *pComp)
1618{
1619 pComp->Value(rStruct: mkNamingAdapt(rValue&: fOK, szName: "OK", rDefault: true));
1620 pComp->Value(rStruct: mkNamingAdapt(rValue&: szMsg, szName: "Message", rDefault: ""));
1621 pComp->Value(rStruct: mkNamingAdapt(rValue&: fWrongPassword, szName: "WrongPassword", rDefault: false));
1622}
1623
1624// *** C4PacketFwd
1625
1626C4PacketFwd::C4PacketFwd()
1627 : fNegativeList(false),
1628 iClientCnt(0) {}
1629
1630bool C4PacketFwd::DoFwdTo(int32_t iClient) const
1631{
1632 for (int32_t i = 0; i < iClientCnt; i++)
1633 if (iClients[i] == iClient)
1634 return !fNegativeList;
1635 return fNegativeList;
1636}
1637
1638void C4PacketFwd::SetData(const C4NetIOPacket &Pkt)
1639{
1640 Data = Pkt.Duplicate();
1641}
1642
1643void C4PacketFwd::SetListType(bool fnNegativeList)
1644{
1645 fNegativeList = fnNegativeList;
1646}
1647
1648void C4PacketFwd::AddClient(int32_t iClient)
1649{
1650 if (iClientCnt + 1 > C4NetMaxClients) return;
1651 // add
1652 iClients[iClientCnt++] = iClient;
1653}
1654
1655void C4PacketFwd::CompileFunc(StdCompiler *pComp)
1656{
1657 pComp->Value(rStruct: mkNamingAdapt(rValue&: fNegativeList, szName: "Negative", rDefault: false));
1658 pComp->Value(rStruct: mkNamingAdapt(rValue: mkIntPackAdapt(rVal&: iClientCnt), szName: "ClientCnt", rDefault: 0));
1659 pComp->Value(rStruct: mkNamingAdapt(rValue: mkArrayAdaptMapS(array: iClients, size: iClientCnt, map: mkIntPackAdapt<int32_t>), szName: "Clients", rDefault: -1));
1660 pComp->Value(rStruct: mkNamingAdapt(rValue&: Data, szName: "Data"));
1661}
1662
1663// *** C4PacketJoinData
1664
1665void C4PacketJoinData::CompileFunc(StdCompiler *pComp)
1666{
1667 pComp->Value(rStruct: mkNamingAdapt(rValue: mkIntPackAdapt(rVal&: iClientID), szName: "ClientID", rDefault: C4ClientIDUnknown));
1668 pComp->Value(rStruct: mkNamingAdapt(rValue: mkIntPackAdapt(rVal&: iStartCtrlTick), szName: "CtrlTick", rDefault: -1));
1669 pComp->Value(rStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: GameStatus, rPar: true), szName: "GameStatus"));
1670 pComp->Value(rStruct: mkNamingAdapt(rValue&: Dynamic, szName: "Dynamic"));
1671 pComp->Value(rStruct&: Parameters);
1672}
1673
1674// C4PacketReadyCheck
1675
1676void C4PacketReadyCheck::CompileFunc(StdCompiler *comp)
1677{
1678 comp->Value(rStruct: mkNamingAdapt(rValue&: client, szName: "Client", rDefault: 0));
1679 comp->Value(rStruct: mkNamingAdapt(rValue&: static_cast<std::underlying_type_t<Data> &>(data), szName: "Data", rDefault: Request));
1680}
1681
1682// *** C4PacketPing
1683
1684C4PacketPing::C4PacketPing(uint32_t iPacketCounter)
1685 : iTime(timeGetTime()),
1686 iPacketCounter(iPacketCounter) {}
1687
1688uint32_t C4PacketPing::getTravelTime() const
1689{
1690 return timeGetTime() - iTime;
1691}
1692
1693void C4PacketPing::CompileFunc(StdCompiler *pComp)
1694{
1695 pComp->Value(rStruct: mkNamingAdapt(rValue&: iTime, szName: "Time", rDefault: 0U));
1696 pComp->Value(rStruct: mkNamingAdapt(rValue&: iPacketCounter, szName: "PacketCounter", rDefault: 0U));
1697}
1698
1699// *** C4PacketResStatus
1700
1701C4PacketResStatus::C4PacketResStatus() {}
1702
1703C4PacketResStatus::C4PacketResStatus(int32_t iResID, const C4Network2ResChunkData &nChunks)
1704 : iResID(iResID), Chunks(nChunks) {}
1705
1706void C4PacketResStatus::CompileFunc(StdCompiler *pComp)
1707{
1708 pComp->Value(rStruct: mkNamingAdapt(rValue&: iResID, szName: "ResID"));
1709 pComp->Value(rStruct: mkNamingAdapt(rValue&: Chunks, szName: "Chunks"));
1710}
1711
1712// *** C4PacketResDiscover
1713
1714C4PacketResDiscover::C4PacketResDiscover()
1715 : iDisIDCnt(0) {}
1716
1717bool C4PacketResDiscover::isIDPresent(int32_t iID) const
1718{
1719 for (int32_t i = 0; i < iDisIDCnt; i++)
1720 if (iDisIDs[i] == iID)
1721 return true;
1722 return false;
1723}
1724
1725bool C4PacketResDiscover::AddDisID(int32_t iID)
1726{
1727 if (iDisIDCnt + 1 >= int32_t(sizeof(iDisIDs) / sizeof(*iDisIDs))) return false;
1728 // add
1729 iDisIDs[iDisIDCnt++] = iID;
1730 return true;
1731}
1732
1733void C4PacketResDiscover::CompileFunc(StdCompiler *pComp)
1734{
1735 pComp->Value(rStruct: mkNamingAdapt(rValue: mkIntPackAdapt(rVal&: iDisIDCnt), szName: "DiscoverCnt", rDefault: 0));
1736 pComp->Value(rStruct: mkNamingAdapt(rValue: mkArrayAdaptS(array: iDisIDs, size: iDisIDCnt), szName: "Discovers", rDefault: -1));
1737}
1738
1739// *** C4PacketResRequest
1740
1741C4PacketResRequest::C4PacketResRequest(int32_t inID, int32_t inChunk)
1742 : iReqID(inID), iReqChunk(inChunk) {}
1743
1744void C4PacketResRequest::CompileFunc(StdCompiler *pComp)
1745{
1746 pComp->Value(rStruct: mkNamingAdapt(rValue&: iReqID, szName: "ResID", rDefault: -1));
1747 pComp->Value(rStruct: mkNamingAdapt(rValue: mkIntPackAdapt(rVal&: iReqChunk), szName: "Chunk", rDefault: -1));
1748}
1749
1750// *** C4PacketControlReq
1751
1752C4PacketControlReq::C4PacketControlReq(int32_t inCtrlTick)
1753 : iCtrlTick(inCtrlTick) {}
1754
1755void C4PacketControlReq::CompileFunc(StdCompiler *pComp)
1756{
1757 pComp->Value(rStruct: mkNamingAdapt(rValue: mkIntPackAdapt(rVal&: iCtrlTick), szName: "CtrlTick", rDefault: -1));
1758}
1759
1760// *** C4PacketActivateReq
1761
1762void C4PacketActivateReq::CompileFunc(StdCompiler *pComp)
1763{
1764 pComp->Value(rStruct: mkNamingAdapt(rValue: mkIntPackAdapt(rVal&: iTick), szName: "Tick", rDefault: -1));
1765}
1766
1767// *** C4PacketControlPkt
1768
1769void C4PacketControlPkt::CompileFunc(StdCompiler *pComp)
1770{
1771 pComp->Value(rStruct: mkNamingAdapt(rValue: mkIntAdaptT<uint8_t>(rValue&: eDelivery), szName: "Delivery", rDefault: CDT_Queue));
1772 pComp->Value(rStruct&: Ctrl);
1773}
1774