LCOV - code coverage report
Current view: top level - source/network - NetServer.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 0 811 0.0 %
Date: 2021-09-24 14:46:47 Functions: 0 76 0.0 %

          Line data    Source code
       1             : /* Copyright (C) 2021 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "NetServer.h"
      21             : 
      22             : #include "NetClient.h"
      23             : #include "NetMessage.h"
      24             : #include "NetSession.h"
      25             : #include "NetServerTurnManager.h"
      26             : #include "NetStats.h"
      27             : 
      28             : #include "lib/external_libraries/enet.h"
      29             : #include "lib/types.h"
      30             : #include "network/StunClient.h"
      31             : #include "ps/CLogger.h"
      32             : #include "ps/ConfigDB.h"
      33             : #include "ps/GUID.h"
      34             : #include "ps/Hashing.h"
      35             : #include "ps/Profile.h"
      36             : #include "ps/Threading.h"
      37             : #include "scriptinterface/ScriptContext.h"
      38             : #include "scriptinterface/ScriptInterface.h"
      39             : #include "scriptinterface/JSON.h"
      40             : #include "simulation2/Simulation2.h"
      41             : #include "simulation2/system/TurnManager.h"
      42             : 
      43             : #if CONFIG2_MINIUPNPC
      44             : #include <miniupnpc/miniwget.h>
      45             : #include <miniupnpc/miniupnpc.h>
      46             : #include <miniupnpc/upnpcommands.h>
      47             : #include <miniupnpc/upnperrors.h>
      48             : #endif
      49             : 
      50             : #include <string>
      51             : 
      52             : /**
      53             :  * Number of peers to allocate for the enet host.
      54             :  * Limited by ENET_PROTOCOL_MAXIMUM_PEER_ID (4096).
      55             :  *
      56             :  * At most 8 players, 32 observers and 1 temporary connection to send the "server full" disconnect-reason.
      57             :  */
      58             : #define MAX_CLIENTS 41
      59             : 
      60             : #define DEFAULT_SERVER_NAME         L"Unnamed Server"
      61             : 
      62             : constexpr int CHANNEL_COUNT = 1;
      63             : constexpr int FAILED_PASSWORD_TRIES_BEFORE_BAN = 3;
      64             : 
      65             : /**
      66             :  * enet_host_service timeout (msecs).
      67             :  * Smaller numbers may hurt performance; larger numbers will
      68             :  * hurt latency responding to messages from game thread.
      69             :  */
      70             : static const int HOST_SERVICE_TIMEOUT = 50;
      71             : 
      72             : /**
      73             :  * Once ping goes above turn length * command delay,
      74             :  * the game will start 'freezing' for other clients while we catch up.
      75             :  * Since commands are sent client -> server -> client, divide by 2.
      76             :  * (duplicated in NetServer.cpp to avoid having to fetch the constants in a header file)
      77             :  */
      78             : constexpr u32 NETWORK_BAD_PING = DEFAULT_TURN_LENGTH * COMMAND_DELAY_MP / 2;
      79             : 
      80             : CNetServer* g_NetServer = NULL;
      81             : 
      82           0 : static CStr DebugName(CNetServerSession* session)
      83             : {
      84           0 :     if (session == NULL)
      85           0 :         return "[unknown host]";
      86           0 :     if (session->GetGUID().empty())
      87           0 :         return "[unauthed host]";
      88           0 :     return "[" + session->GetGUID().substr(0, 8) + "...]";
      89             : }
      90             : 
      91             : /**
      92             :  * Async task for receiving the initial game state to be forwarded to another
      93             :  * client that is rejoining an in-progress network game.
      94             :  */
      95             : class CNetFileReceiveTask_ServerRejoin : public CNetFileReceiveTask
      96             : {
      97             :     NONCOPYABLE(CNetFileReceiveTask_ServerRejoin);
      98             : public:
      99             :     CNetFileReceiveTask_ServerRejoin(CNetServerWorker& server, u32 hostID)
     100           0 :         : m_Server(server), m_RejoinerHostID(hostID)
     101             :     {
     102             :     }
     103             : 
     104           0 :     virtual void OnComplete()
     105             :     {
     106             :         // We've received the game state from an existing player - now
     107             :         // we need to send it onwards to the newly rejoining player
     108             : 
     109             :         // Find the session corresponding to the rejoining host (if any)
     110           0 :         CNetServerSession* session = NULL;
     111           0 :         for (CNetServerSession* serverSession : m_Server.m_Sessions)
     112             :         {
     113           0 :             if (serverSession->GetHostID() == m_RejoinerHostID)
     114             :             {
     115             :                 session = serverSession;
     116             :                 break;
     117             :             }
     118             :         }
     119             : 
     120           0 :         if (!session)
     121             :         {
     122           0 :             LOGMESSAGE("Net server: rejoining client disconnected before we sent to it");
     123           0 :             return;
     124             :         }
     125             : 
     126             :         // Store the received state file, and tell the client to start downloading it from us
     127             :         // TODO: this will get kind of confused if there's multiple clients downloading in parallel;
     128             :         // they'll race and get whichever happens to be the latest received by the server,
     129             :         // which should still work but isn't great
     130           0 :         m_Server.m_JoinSyncFile = m_Buffer;
     131             : 
     132             :         // Send the init attributes alongside - these should be correct since the game should be started.
     133           0 :         CJoinSyncStartMessage message;
     134           0 :         message.m_InitAttributes = Script::StringifyJSON(ScriptRequest(m_Server.GetScriptInterface()), &m_Server.m_InitAttributes);
     135           0 :         session->SendMessage(&message);
     136             :     }
     137             : 
     138             : private:
     139             :     CNetServerWorker& m_Server;
     140             :     u32 m_RejoinerHostID;
     141             : };
     142             : 
     143             : /*
     144             :  * XXX: We use some non-threadsafe functions from the worker thread.
     145             :  * See http://trac.wildfiregames.com/ticket/654
     146             :  */
     147             : 
     148           0 : CNetServerWorker::CNetServerWorker(bool useLobbyAuth, int autostartPlayers) :
     149             :     m_AutostartPlayers(autostartPlayers),
     150             :     m_LobbyAuth(useLobbyAuth),
     151             :     m_Shutdown(false),
     152             :     m_ScriptInterface(NULL),
     153             :     m_NextHostID(1), m_Host(NULL), m_ControllerGUID(), m_Stats(NULL),
     154           0 :     m_LastConnectionCheck(0)
     155             : {
     156           0 :     m_State = SERVER_STATE_UNCONNECTED;
     157             : 
     158           0 :     m_ServerTurnManager = NULL;
     159             : 
     160           0 :     m_ServerName = DEFAULT_SERVER_NAME;
     161           0 : }
     162             : 
     163           0 : CNetServerWorker::~CNetServerWorker()
     164             : {
     165           0 :     if (m_State != SERVER_STATE_UNCONNECTED)
     166             :     {
     167             :         // Tell the thread to shut down
     168           0 :         {
     169           0 :             std::lock_guard<std::mutex> lock(m_WorkerMutex);
     170           0 :             m_Shutdown = true;
     171             :         }
     172             : 
     173             :         // Wait for it to shut down cleanly
     174           0 :         m_WorkerThread.join();
     175             :     }
     176             : 
     177             : #if CONFIG2_MINIUPNPC
     178           0 :     if (m_UPnPThread.joinable())
     179           0 :         m_UPnPThread.detach();
     180             : #endif
     181             : 
     182             :     // Clean up resources
     183             : 
     184           0 :     delete m_Stats;
     185             : 
     186           0 :     for (CNetServerSession* session : m_Sessions)
     187             :     {
     188           0 :         session->DisconnectNow(NDR_SERVER_SHUTDOWN);
     189           0 :         delete session;
     190             :     }
     191             : 
     192           0 :     if (m_Host)
     193           0 :         enet_host_destroy(m_Host);
     194             : 
     195           0 :     delete m_ServerTurnManager;
     196           0 : }
     197             : 
     198           0 : void CNetServerWorker::SetPassword(const CStr& hashedPassword)
     199             : {
     200           0 :     m_Password = hashedPassword;
     201           0 : }
     202             : 
     203             : 
     204           0 : void CNetServerWorker::SetControllerSecret(const std::string& secret)
     205             : {
     206           0 :     m_ControllerSecret = secret;
     207           0 : }
     208             : 
     209             : 
     210           0 : bool CNetServerWorker::CheckPassword(const std::string& password, const std::string& salt) const
     211             : {
     212           0 :     return HashCryptographically(m_Password, salt) == password;
     213             : }
     214             : 
     215             : 
     216           0 : bool CNetServerWorker::SetupConnection(const u16 port)
     217             : {
     218           0 :     ENSURE(m_State == SERVER_STATE_UNCONNECTED);
     219           0 :     ENSURE(!m_Host);
     220             : 
     221             :     // Bind to default host
     222           0 :     ENetAddress addr;
     223           0 :     addr.host = ENET_HOST_ANY;
     224           0 :     addr.port = port;
     225             : 
     226             :     // Create ENet server
     227           0 :     m_Host = enet_host_create(&addr, MAX_CLIENTS, CHANNEL_COUNT, 0, 0);
     228           0 :     if (!m_Host)
     229             :     {
     230           0 :         LOGERROR("Net server: enet_host_create failed");
     231           0 :         return false;
     232             :     }
     233             : 
     234           0 :     m_Stats = new CNetStatsTable();
     235           0 :     if (CProfileViewer::IsInitialised())
     236           0 :         g_ProfileViewer.AddRootTable(m_Stats);
     237             : 
     238           0 :     m_State = SERVER_STATE_PREGAME;
     239             : 
     240             :     // Launch the worker thread
     241           0 :     m_WorkerThread = std::thread(Threading::HandleExceptions<RunThread>::Wrapper, this);
     242             : 
     243             : #if CONFIG2_MINIUPNPC
     244             :     // Launch the UPnP thread
     245           0 :     m_UPnPThread = std::thread(Threading::HandleExceptions<SetupUPnP>::Wrapper);
     246             : #endif
     247             : 
     248           0 :     return true;
     249             : }
     250             : 
     251             : #if CONFIG2_MINIUPNPC
     252           0 : void CNetServerWorker::SetupUPnP()
     253             : {
     254           0 :     debug_SetThreadName("UPnP");
     255             : 
     256             :     // Values we want to set.
     257           0 :     char psPort[6];
     258           0 :     sprintf_s(psPort, ARRAY_SIZE(psPort), "%d", PS_DEFAULT_PORT);
     259           0 :     const char* leaseDuration = "0"; // Indefinite/permanent lease duration.
     260           0 :     const char* description = "0AD Multiplayer";
     261           0 :     const char* protocall = "UDP";
     262           0 :     char internalIPAddress[64];
     263           0 :     char externalIPAddress[40];
     264             : 
     265             :     // Variables to hold the values that actually get set.
     266           0 :     char intClient[40];
     267           0 :     char intPort[6];
     268           0 :     char duration[16];
     269             : 
     270             :     // Intermediate variables.
     271           0 :     bool allocatedUrls = false;
     272           0 :     struct UPNPUrls urls;
     273           0 :     struct IGDdatas data;
     274           0 :     struct UPNPDev* devlist = NULL;
     275             : 
     276             :     // Make sure everything is properly freed.
     277           0 :     std::function<void()> freeUPnP = [&allocatedUrls, &urls, &devlist]()
     278             :     {
     279           0 :         if (allocatedUrls)
     280           0 :             FreeUPNPUrls(&urls);
     281           0 :         freeUPNPDevlist(devlist);
     282             :         // IGDdatas does not need to be freed according to UPNP_GetIGDFromUrl
     283           0 :     };
     284             : 
     285             :     // Cached root descriptor URL.
     286           0 :     std::string rootDescURL;
     287           0 :     CFG_GET_VAL("network.upnprootdescurl", rootDescURL);
     288           0 :     if (!rootDescURL.empty())
     289           0 :         LOGMESSAGE("Net server: attempting to use cached root descriptor URL: %s", rootDescURL.c_str());
     290             : 
     291           0 :     int ret = 0;
     292             : 
     293             :     // Try a cached URL first
     294           0 :     if (!rootDescURL.empty() && UPNP_GetIGDFromUrl(rootDescURL.c_str(), &urls, &data, internalIPAddress, sizeof(internalIPAddress)))
     295             :     {
     296           0 :         LOGMESSAGE("Net server: using cached IGD = %s", urls.controlURL);
     297           0 :         ret = 1;
     298             :     }
     299             :     // No cached URL, or it did not respond. Try getting a valid UPnP device for 10 seconds.
     300             : #if defined(MINIUPNPC_API_VERSION) && MINIUPNPC_API_VERSION >= 14
     301           0 :     else if ((devlist = upnpDiscover(10000, 0, 0, 0, 0, 2, 0)) != NULL)
     302             : #else
     303             :     else if ((devlist = upnpDiscover(10000, 0, 0, 0, 0, 0)) != NULL)
     304             : #endif
     305             :     {
     306           0 :         ret = UPNP_GetValidIGD(devlist, &urls, &data, internalIPAddress, sizeof(internalIPAddress));
     307           0 :         allocatedUrls = ret != 0; // urls is allocated on non-zero return values
     308             :     }
     309             :     else
     310             :     {
     311           0 :         LOGMESSAGE("Net server: upnpDiscover failed and no working cached URL.");
     312           0 :         freeUPnP();
     313           0 :         return;
     314             :     }
     315             : 
     316           0 :     switch (ret)
     317             :     {
     318           0 :     case 0:
     319           0 :         LOGMESSAGE("Net server: No IGD found");
     320           0 :         break;
     321           0 :     case 1:
     322           0 :         LOGMESSAGE("Net server: found valid IGD = %s", urls.controlURL);
     323           0 :         break;
     324           0 :     case 2:
     325           0 :         LOGMESSAGE("Net server: found a valid, not connected IGD = %s, will try to continue anyway", urls.controlURL);
     326           0 :         break;
     327           0 :     case 3:
     328           0 :         LOGMESSAGE("Net server: found a UPnP device unrecognized as IGD = %s, will try to continue anyway", urls.controlURL);
     329           0 :         break;
     330           0 :     default:
     331           0 :         debug_warn(L"Unrecognized return value from UPNP_GetValidIGD");
     332             :     }
     333             : 
     334             :     // Try getting our external/internet facing IP. TODO: Display this on the game-setup page for conviniance.
     335           0 :     ret = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, externalIPAddress);
     336           0 :     if (ret != UPNPCOMMAND_SUCCESS)
     337             :     {
     338           0 :         LOGMESSAGE("Net server: GetExternalIPAddress failed with code %d (%s)", ret, strupnperror(ret));
     339           0 :         freeUPnP();
     340             :         return;
     341             :     }
     342           0 :     LOGMESSAGE("Net server: ExternalIPAddress = %s", externalIPAddress);
     343             : 
     344             :     // Try to setup port forwarding.
     345           0 :     ret = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, psPort, psPort,
     346             :                             internalIPAddress, description, protocall, 0, leaseDuration);
     347           0 :     if (ret != UPNPCOMMAND_SUCCESS)
     348             :     {
     349           0 :         LOGMESSAGE("Net server: AddPortMapping(%s, %s, %s) failed with code %d (%s)",
     350             :                psPort, psPort, internalIPAddress, ret, strupnperror(ret));
     351           0 :         freeUPnP();
     352             :         return;
     353             :     }
     354             : 
     355             :     // Check that the port was actually forwarded.
     356           0 :     ret = UPNP_GetSpecificPortMappingEntry(urls.controlURL,
     357             :                                      data.first.servicetype,
     358             :                                      psPort, protocall,
     359             : #if defined(MINIUPNPC_API_VERSION) && MINIUPNPC_API_VERSION >= 10
     360             :                                      NULL/*remoteHost*/,
     361             : #endif
     362             :                                      intClient, intPort, NULL/*desc*/,
     363             :                                      NULL/*enabled*/, duration);
     364             : 
     365           0 :     if (ret != UPNPCOMMAND_SUCCESS)
     366             :     {
     367           0 :         LOGMESSAGE("Net server: GetSpecificPortMappingEntry() failed with code %d (%s)", ret, strupnperror(ret));
     368           0 :         freeUPnP();
     369             :         return;
     370             :     }
     371             : 
     372           0 :     LOGMESSAGE("Net server: External %s:%s %s is redirected to internal %s:%s (duration=%s)",
     373             :                    externalIPAddress, psPort, protocall, intClient, intPort, duration);
     374             : 
     375             :     // Cache root descriptor URL to try to avoid discovery next time.
     376           0 :     g_ConfigDB.SetValueString(CFG_USER, "network.upnprootdescurl", urls.controlURL);
     377           0 :     g_ConfigDB.WriteValueToFile(CFG_USER, "network.upnprootdescurl", urls.controlURL);
     378           0 :     LOGMESSAGE("Net server: cached UPnP root descriptor URL as %s", urls.controlURL);
     379             : 
     380           0 :     freeUPnP();
     381             : }
     382             : #endif // CONFIG2_MINIUPNPC
     383             : 
     384           0 : bool CNetServerWorker::SendMessage(ENetPeer* peer, const CNetMessage* message)
     385             : {
     386           0 :     ENSURE(m_Host);
     387             : 
     388           0 :     CNetServerSession* session = static_cast<CNetServerSession*>(peer->data);
     389             : 
     390           0 :     return CNetHost::SendMessage(message, peer, DebugName(session).c_str());
     391             : }
     392             : 
     393           0 : bool CNetServerWorker::Broadcast(const CNetMessage* message, const std::vector<NetServerSessionState>& targetStates)
     394             : {
     395           0 :     ENSURE(m_Host);
     396             : 
     397           0 :     bool ok = true;
     398             : 
     399             :     // TODO: this does lots of repeated message serialisation if we have lots
     400             :     // of remote peers; could do it more efficiently if that's a real problem
     401             : 
     402           0 :     for (CNetServerSession* session : m_Sessions)
     403           0 :         if (std::find(targetStates.begin(), targetStates.end(), static_cast<NetServerSessionState>(session->GetCurrState())) != targetStates.end() &&
     404           0 :             !session->SendMessage(message))
     405             :             ok = false;
     406             : 
     407           0 :     return ok;
     408             : }
     409             : 
     410           0 : void CNetServerWorker::RunThread(CNetServerWorker* data)
     411             : {
     412           0 :     debug_SetThreadName("NetServer");
     413             : 
     414           0 :     data->Run();
     415           0 : }
     416             : 
     417           0 : void CNetServerWorker::Run()
     418             : {
     419             :     // The script context uses the profiler and therefore the thread must be registered before the context is created
     420           0 :     g_Profiler2.RegisterCurrentThread("Net server");
     421             : 
     422             :     // We create a new ScriptContext for this network thread, with a single ScriptInterface.
     423           0 :     std::shared_ptr<ScriptContext> netServerContext = ScriptContext::CreateContext();
     424           0 :     m_ScriptInterface = new ScriptInterface("Engine", "Net server", netServerContext);
     425           0 :     m_InitAttributes.init(m_ScriptInterface->GetGeneralJSContext(), JS::UndefinedValue());
     426             : 
     427           0 :     while (true)
     428             :     {
     429           0 :         if (!RunStep())
     430             :             break;
     431             : 
     432             :         // Implement autostart mode
     433           0 :         if (m_State == SERVER_STATE_PREGAME && (int)m_PlayerAssignments.size() == m_AutostartPlayers)
     434           0 :             StartGame(Script::StringifyJSON(ScriptRequest(m_ScriptInterface), &m_InitAttributes));
     435             : 
     436             :         // Update profiler stats
     437           0 :         m_Stats->LatchHostState(m_Host);
     438             :     }
     439             : 
     440             :     // Clear roots before deleting their context
     441           0 :     m_SavedCommands.clear();
     442             : 
     443           0 :     SAFE_DELETE(m_ScriptInterface);
     444           0 : }
     445             : 
     446           0 : bool CNetServerWorker::RunStep()
     447             : {
     448             :     // Check for messages from the game thread.
     449             :     // (Do as little work as possible while the mutex is held open,
     450             :     // to avoid performance problems and deadlocks.)
     451             : 
     452           0 :     m_ScriptInterface->GetContext()->MaybeIncrementalGC(0.5f);
     453             : 
     454           0 :     ScriptRequest rq(m_ScriptInterface);
     455             : 
     456           0 :     std::vector<bool> newStartGame;
     457           0 :     std::vector<std::string> newGameAttributes;
     458           0 :     std::vector<std::pair<CStr, CStr>> newLobbyAuths;
     459           0 :     std::vector<u32> newTurnLength;
     460             : 
     461           0 :     {
     462           0 :         std::lock_guard<std::mutex> lock(m_WorkerMutex);
     463             : 
     464           0 :         if (m_Shutdown)
     465           0 :             return false;
     466             : 
     467           0 :         newStartGame.swap(m_StartGameQueue);
     468           0 :         newGameAttributes.swap(m_InitAttributesQueue);
     469           0 :         newLobbyAuths.swap(m_LobbyAuthQueue);
     470           0 :         newTurnLength.swap(m_TurnLengthQueue);
     471             :     }
     472             : 
     473           0 :     if (!newGameAttributes.empty())
     474             :     {
     475           0 :         if (m_State != SERVER_STATE_UNCONNECTED && m_State != SERVER_STATE_PREGAME)
     476           0 :             LOGERROR("NetServer: Init Attributes cannot be changed after the server starts loading.");
     477             :         else
     478             :         {
     479           0 :             JS::RootedValue gameAttributesVal(rq.cx);
     480           0 :             Script::ParseJSON(rq, newGameAttributes.back(), &gameAttributesVal);
     481           0 :             m_InitAttributes = gameAttributesVal;
     482             :         }
     483             :     }
     484             : 
     485           0 :     if (!newTurnLength.empty())
     486           0 :         SetTurnLength(newTurnLength.back());
     487             : 
     488           0 :     while (!newLobbyAuths.empty())
     489             :     {
     490           0 :         const std::pair<CStr, CStr>& auth = newLobbyAuths.back();
     491           0 :         ProcessLobbyAuth(auth.first, auth.second);
     492           0 :         newLobbyAuths.pop_back();
     493             :     }
     494             : 
     495             :     // Perform file transfers
     496           0 :     for (CNetServerSession* session : m_Sessions)
     497           0 :         session->GetFileTransferer().Poll();
     498             : 
     499           0 :     CheckClientConnections();
     500             : 
     501             :     // Process network events:
     502             : 
     503           0 :     ENetEvent event;
     504           0 :     int status = enet_host_service(m_Host, &event, HOST_SERVICE_TIMEOUT);
     505           0 :     if (status < 0)
     506             :     {
     507           0 :         LOGERROR("CNetServerWorker: enet_host_service failed (%d)", status);
     508             :         // TODO: notify game that the server has shut down
     509           0 :         return false;
     510             :     }
     511             : 
     512           0 :     if (status == 0)
     513             :     {
     514             :         // Reached timeout with no events - try again
     515             :         return true;
     516             :     }
     517             : 
     518             :     // Process the event:
     519             : 
     520           0 :     switch (event.type)
     521             :     {
     522           0 :     case ENET_EVENT_TYPE_CONNECT:
     523           0 :     {
     524             :         // Report the client address
     525           0 :         char hostname[256] = "(error)";
     526           0 :         enet_address_get_host_ip(&event.peer->address, hostname, ARRAY_SIZE(hostname));
     527           0 :         LOGMESSAGE("Net server: Received connection from %s:%u", hostname, (unsigned int)event.peer->address.port);
     528             : 
     529             :         // Set up a session object for this peer
     530             : 
     531           0 :         CNetServerSession* session = new CNetServerSession(*this, event.peer);
     532             : 
     533           0 :         m_Sessions.push_back(session);
     534             : 
     535           0 :         SetupSession(session);
     536             : 
     537           0 :         ENSURE(event.peer->data == NULL);
     538           0 :         event.peer->data = session;
     539             : 
     540           0 :         HandleConnect(session);
     541             : 
     542           0 :         break;
     543             :     }
     544             : 
     545           0 :     case ENET_EVENT_TYPE_DISCONNECT:
     546           0 :     {
     547             :         // If there is an active session with this peer, then reset and delete it
     548             : 
     549           0 :         CNetServerSession* session = static_cast<CNetServerSession*>(event.peer->data);
     550           0 :         if (session)
     551             :         {
     552           0 :             LOGMESSAGE("Net server: Disconnected %s", DebugName(session).c_str());
     553             : 
     554             :             // Remove the session first, so we won't send player-update messages to it
     555             :             // when updating the FSM
     556           0 :             m_Sessions.erase(remove(m_Sessions.begin(), m_Sessions.end(), session), m_Sessions.end());
     557             : 
     558           0 :             session->Update((uint)NMT_CONNECTION_LOST, NULL);
     559             : 
     560           0 :             delete session;
     561           0 :             event.peer->data = NULL;
     562             :         }
     563             : 
     564           0 :         if (m_State == SERVER_STATE_LOADING)
     565           0 :             CheckGameLoadStatus(NULL);
     566             : 
     567           0 :         break;
     568             :     }
     569             : 
     570           0 :     case ENET_EVENT_TYPE_RECEIVE:
     571           0 :     {
     572             :         // If there is an active session with this peer, then process the message
     573             : 
     574           0 :         CNetServerSession* session = static_cast<CNetServerSession*>(event.peer->data);
     575           0 :         if (session)
     576             :         {
     577             :             // Create message from raw data
     578           0 :             CNetMessage* msg = CNetMessageFactory::CreateMessage(event.packet->data, event.packet->dataLength, GetScriptInterface());
     579           0 :             if (msg)
     580             :             {
     581           0 :                 LOGMESSAGE("Net server: Received message %s of size %lu from %s", msg->ToString().c_str(), (unsigned long)msg->GetSerializedLength(), DebugName(session).c_str());
     582             : 
     583           0 :                 HandleMessageReceive(msg, session);
     584             : 
     585           0 :                 delete msg;
     586             :             }
     587             :         }
     588             : 
     589             :         // Done using the packet
     590           0 :         enet_packet_destroy(event.packet);
     591             : 
     592             :         break;
     593             :     }
     594             : 
     595             :     case ENET_EVENT_TYPE_NONE:
     596             :         break;
     597             :     }
     598             : 
     599             :     return true;
     600             : }
     601             : 
     602           0 : void CNetServerWorker::CheckClientConnections()
     603             : {
     604             :     // Send messages at most once per second
     605           0 :     std::time_t now = std::time(nullptr);
     606           0 :     if (now <= m_LastConnectionCheck)
     607             :         return;
     608             : 
     609           0 :     m_LastConnectionCheck = now;
     610             : 
     611           0 :     for (size_t i = 0; i < m_Sessions.size(); ++i)
     612             :     {
     613           0 :         u32 lastReceived = m_Sessions[i]->GetLastReceivedTime();
     614           0 :         u32 meanRTT = m_Sessions[i]->GetMeanRTT();
     615             : 
     616           0 :         CNetMessage* message = nullptr;
     617             : 
     618             :         // Report if we didn't hear from the client since few seconds
     619           0 :         if (lastReceived > NETWORK_WARNING_TIMEOUT)
     620             :         {
     621           0 :             CClientTimeoutMessage* msg = new CClientTimeoutMessage();
     622           0 :             msg->m_GUID = m_Sessions[i]->GetGUID();
     623           0 :             msg->m_LastReceivedTime = lastReceived;
     624           0 :             message = msg;
     625             :         }
     626             :         // Report if the client has bad ping
     627           0 :         else if (meanRTT > NETWORK_BAD_PING)
     628             :         {
     629           0 :             CClientPerformanceMessage* msg = new CClientPerformanceMessage();
     630           0 :             CClientPerformanceMessage::S_m_Clients client;
     631           0 :             client.m_GUID = m_Sessions[i]->GetGUID();
     632           0 :             client.m_MeanRTT = meanRTT;
     633           0 :             msg->m_Clients.push_back(client);
     634           0 :             message = msg;
     635             :         }
     636             : 
     637             :         // Send to all clients except the affected one
     638             :         // (since that will show the locally triggered warning instead).
     639             :         // Also send it to clients that finished the loading screen while
     640             :         // the game is still waiting for other clients to finish the loading screen.
     641           0 :         if (message)
     642           0 :             for (size_t j = 0; j < m_Sessions.size(); ++j)
     643             :             {
     644           0 :                 if (i != j && (
     645           0 :                     (m_Sessions[j]->GetCurrState() == NSS_PREGAME && m_State == SERVER_STATE_PREGAME) ||
     646           0 :                     m_Sessions[j]->GetCurrState() == NSS_INGAME))
     647             :                 {
     648           0 :                     m_Sessions[j]->SendMessage(message);
     649             :                 }
     650             :             }
     651             : 
     652           0 :         SAFE_DELETE(message);
     653             :     }
     654             : }
     655             : 
     656           0 : void CNetServerWorker::HandleMessageReceive(const CNetMessage* message, CNetServerSession* session)
     657             : {
     658             :     // Handle non-FSM messages first
     659           0 :     Status status = session->GetFileTransferer().HandleMessageReceive(*message);
     660           0 :     if (status != INFO::SKIPPED)
     661             :         return;
     662             : 
     663           0 :     if (message->GetType() == NMT_FILE_TRANSFER_REQUEST)
     664             :     {
     665           0 :         CFileTransferRequestMessage* reqMessage = (CFileTransferRequestMessage*)message;
     666             : 
     667             :         // Rejoining client got our JoinSyncStart after we received the state from
     668             :         // another client, and has now requested that we forward it to them
     669             : 
     670           0 :         ENSURE(!m_JoinSyncFile.empty());
     671           0 :         session->GetFileTransferer().StartResponse(reqMessage->m_RequestID, m_JoinSyncFile);
     672             : 
     673           0 :         return;
     674             :     }
     675             : 
     676             :     // Update FSM
     677           0 :     if (!session->Update(message->GetType(), (void*)message))
     678           0 :         LOGERROR("Net server: Error running FSM update (type=%d state=%d)", (int)message->GetType(), (int)session->GetCurrState());
     679             : }
     680             : 
     681           0 : void CNetServerWorker::SetupSession(CNetServerSession* session)
     682             : {
     683           0 :     void* context = session;
     684             : 
     685             :     // Set up transitions for session
     686             : 
     687           0 :     session->AddTransition(NSS_UNCONNECTED, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED);
     688             : 
     689           0 :     session->AddTransition(NSS_HANDSHAKE, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED);
     690           0 :     session->AddTransition(NSS_HANDSHAKE, (uint)NMT_CLIENT_HANDSHAKE, NSS_AUTHENTICATE, (void*)&OnClientHandshake, context);
     691             : 
     692           0 :     session->AddTransition(NSS_LOBBY_AUTHENTICATE, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED);
     693           0 :     session->AddTransition(NSS_LOBBY_AUTHENTICATE, (uint)NMT_AUTHENTICATE, NSS_PREGAME, (void*)&OnAuthenticate, context);
     694             : 
     695           0 :     session->AddTransition(NSS_AUTHENTICATE, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED);
     696           0 :     session->AddTransition(NSS_AUTHENTICATE, (uint)NMT_AUTHENTICATE, NSS_PREGAME, (void*)&OnAuthenticate, context);
     697             : 
     698           0 :     session->AddTransition(NSS_PREGAME, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED, (void*)&OnDisconnect, context);
     699           0 :     session->AddTransition(NSS_PREGAME, (uint)NMT_CHAT, NSS_PREGAME, (void*)&OnChat, context);
     700           0 :     session->AddTransition(NSS_PREGAME, (uint)NMT_READY, NSS_PREGAME, (void*)&OnReady, context);
     701           0 :     session->AddTransition(NSS_PREGAME, (uint)NMT_CLEAR_ALL_READY, NSS_PREGAME, (void*)&OnClearAllReady, context);
     702           0 :     session->AddTransition(NSS_PREGAME, (uint)NMT_GAME_SETUP, NSS_PREGAME, (void*)&OnGameSetup, context);
     703           0 :     session->AddTransition(NSS_PREGAME, (uint)NMT_ASSIGN_PLAYER, NSS_PREGAME, (void*)&OnAssignPlayer, context);
     704           0 :     session->AddTransition(NSS_PREGAME, (uint)NMT_KICKED, NSS_PREGAME, (void*)&OnKickPlayer, context);
     705           0 :     session->AddTransition(NSS_PREGAME, (uint)NMT_GAME_START, NSS_PREGAME, (void*)&OnGameStart, context);
     706           0 :     session->AddTransition(NSS_PREGAME, (uint)NMT_LOADED_GAME, NSS_INGAME, (void*)&OnLoadedGame, context);
     707             : 
     708           0 :     session->AddTransition(NSS_JOIN_SYNCING, (uint)NMT_KICKED, NSS_JOIN_SYNCING, (void*)&OnKickPlayer, context);
     709           0 :     session->AddTransition(NSS_JOIN_SYNCING, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED, (void*)&OnDisconnect, context);
     710           0 :     session->AddTransition(NSS_JOIN_SYNCING, (uint)NMT_LOADED_GAME, NSS_INGAME, (void*)&OnJoinSyncingLoadedGame, context);
     711             : 
     712           0 :     session->AddTransition(NSS_INGAME, (uint)NMT_REJOINED, NSS_INGAME, (void*)&OnRejoined, context);
     713           0 :     session->AddTransition(NSS_INGAME, (uint)NMT_KICKED, NSS_INGAME, (void*)&OnKickPlayer, context);
     714           0 :     session->AddTransition(NSS_INGAME, (uint)NMT_CLIENT_PAUSED, NSS_INGAME, (void*)&OnClientPaused, context);
     715           0 :     session->AddTransition(NSS_INGAME, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED, (void*)&OnDisconnect, context);
     716           0 :     session->AddTransition(NSS_INGAME, (uint)NMT_CHAT, NSS_INGAME, (void*)&OnChat, context);
     717           0 :     session->AddTransition(NSS_INGAME, (uint)NMT_SIMULATION_COMMAND, NSS_INGAME, (void*)&OnSimulationCommand, context);
     718           0 :     session->AddTransition(NSS_INGAME, (uint)NMT_SYNC_CHECK, NSS_INGAME, (void*)&OnSyncCheck, context);
     719           0 :     session->AddTransition(NSS_INGAME, (uint)NMT_END_COMMAND_BATCH, NSS_INGAME, (void*)&OnEndCommandBatch, context);
     720             : 
     721             :     // Set first state
     722           0 :     session->SetFirstState(NSS_HANDSHAKE);
     723           0 : }
     724             : 
     725           0 : bool CNetServerWorker::HandleConnect(CNetServerSession* session)
     726             : {
     727           0 :     if (std::find(m_BannedIPs.begin(), m_BannedIPs.end(), session->GetIPAddress()) != m_BannedIPs.end())
     728             :     {
     729           0 :         session->Disconnect(NDR_BANNED);
     730           0 :         return false;
     731             :     }
     732             : 
     733           0 :     CSrvHandshakeMessage handshake;
     734           0 :     handshake.m_Magic = PS_PROTOCOL_MAGIC;
     735           0 :     handshake.m_ProtocolVersion = PS_PROTOCOL_VERSION;
     736           0 :     handshake.m_SoftwareVersion = PS_PROTOCOL_VERSION;
     737           0 :     return session->SendMessage(&handshake);
     738             : }
     739             : 
     740           0 : void CNetServerWorker::OnUserJoin(CNetServerSession* session)
     741             : {
     742           0 :     AddPlayer(session->GetGUID(), session->GetUserName());
     743             : 
     744           0 :     CPlayerAssignmentMessage assignMessage;
     745           0 :     ConstructPlayerAssignmentMessage(assignMessage);
     746           0 :     session->SendMessage(&assignMessage);
     747           0 : }
     748             : 
     749           0 : void CNetServerWorker::OnUserLeave(CNetServerSession* session)
     750             : {
     751           0 :     std::vector<CStr>::iterator pausing = std::find(m_PausingPlayers.begin(), m_PausingPlayers.end(), session->GetGUID());
     752           0 :     if (pausing != m_PausingPlayers.end())
     753           0 :         m_PausingPlayers.erase(pausing);
     754             : 
     755           0 :     RemovePlayer(session->GetGUID());
     756             : 
     757           0 :     if (m_ServerTurnManager && session->GetCurrState() != NSS_JOIN_SYNCING)
     758           0 :         m_ServerTurnManager->UninitialiseClient(session->GetHostID());
     759             : 
     760             :     // TODO: ought to switch the player controlled by that client
     761             :     // back to AI control, or something?
     762           0 : }
     763             : 
     764           0 : void CNetServerWorker::AddPlayer(const CStr& guid, const CStrW& name)
     765             : {
     766             :     // Find all player IDs in active use; we mustn't give them to a second player (excluding the unassigned ID: -1)
     767           0 :     std::set<i32> usedIDs;
     768           0 :     for (const std::pair<const CStr, PlayerAssignment>& p : m_PlayerAssignments)
     769           0 :         if (p.second.m_Enabled && p.second.m_PlayerID != -1)
     770           0 :             usedIDs.insert(p.second.m_PlayerID);
     771             : 
     772             :     // If the player is rejoining after disconnecting, try to give them
     773             :     // back their old player ID. Don't do this in pregame however,
     774             :     // as that ID might be invalid for various reasons.
     775             : 
     776           0 :     i32 playerID = -1;
     777             : 
     778           0 :     if (m_State != SERVER_STATE_UNCONNECTED && m_State != SERVER_STATE_PREGAME)
     779             :     {
     780             :         // Try to match GUID first
     781           0 :         for (PlayerAssignmentMap::iterator it = m_PlayerAssignments.begin(); it != m_PlayerAssignments.end(); ++it)
     782             :         {
     783           0 :             if (!it->second.m_Enabled && it->first == guid && usedIDs.find(it->second.m_PlayerID) == usedIDs.end())
     784             :             {
     785           0 :                 playerID = it->second.m_PlayerID;
     786           0 :                 m_PlayerAssignments.erase(it); // delete the old mapping, since we've got a new one now
     787           0 :                 goto found;
     788             :             }
     789             :         }
     790             : 
     791             :         // Try to match username next
     792           0 :         for (PlayerAssignmentMap::iterator it = m_PlayerAssignments.begin(); it != m_PlayerAssignments.end(); ++it)
     793             :         {
     794           0 :             if (!it->second.m_Enabled && it->second.m_Name == name && usedIDs.find(it->second.m_PlayerID) == usedIDs.end())
     795             :             {
     796           0 :                 playerID = it->second.m_PlayerID;
     797           0 :                 m_PlayerAssignments.erase(it); // delete the old mapping, since we've got a new one now
     798           0 :                 goto found;
     799             :             }
     800             :         }
     801             :     }
     802             : 
     803           0 : found:
     804           0 :     PlayerAssignment assignment;
     805           0 :     assignment.m_Enabled = true;
     806           0 :     assignment.m_Name = name;
     807           0 :     assignment.m_PlayerID = playerID;
     808           0 :     assignment.m_Status = 0;
     809           0 :     m_PlayerAssignments[guid] = assignment;
     810             : 
     811             :     // Send the new assignments to all currently active players
     812             :     // (which does not include the one that's just joining)
     813           0 :     SendPlayerAssignments();
     814           0 : }
     815             : 
     816           0 : void CNetServerWorker::RemovePlayer(const CStr& guid)
     817             : {
     818           0 :     m_PlayerAssignments[guid].m_Enabled = false;
     819             : 
     820           0 :     SendPlayerAssignments();
     821           0 : }
     822             : 
     823           0 : void CNetServerWorker::ClearAllPlayerReady()
     824             : {
     825           0 :     for (std::pair<const CStr, PlayerAssignment>& p : m_PlayerAssignments)
     826           0 :         if (p.second.m_Status != 2)
     827           0 :             p.second.m_Status = 0;
     828             : 
     829           0 :     SendPlayerAssignments();
     830           0 : }
     831             : 
     832           0 : void CNetServerWorker::KickPlayer(const CStrW& playerName, const bool ban)
     833             : {
     834             :     // Find the user with that name
     835           0 :     std::vector<CNetServerSession*>::iterator it = std::find_if(m_Sessions.begin(), m_Sessions.end(),
     836           0 :         [&](CNetServerSession* session) { return session->GetUserName() == playerName; });
     837             : 
     838             :     // and return if no one or the host has that name
     839           0 :     if (it == m_Sessions.end() || (*it)->GetGUID() == m_ControllerGUID)
     840           0 :         return;
     841             : 
     842           0 :     if (ban)
     843             :     {
     844             :         // Remember name
     845           0 :         if (std::find(m_BannedPlayers.begin(), m_BannedPlayers.end(), playerName) == m_BannedPlayers.end())
     846           0 :             m_BannedPlayers.push_back(m_LobbyAuth ? CStrW(playerName.substr(0, playerName.find(L" ("))) : playerName);
     847             : 
     848             :         // Remember IP address
     849           0 :         u32 ipAddress = (*it)->GetIPAddress();
     850           0 :         if (std::find(m_BannedIPs.begin(), m_BannedIPs.end(), ipAddress) == m_BannedIPs.end())
     851           0 :             m_BannedIPs.push_back(ipAddress);
     852             :     }
     853             : 
     854             :     // Disconnect that user
     855           0 :     (*it)->Disconnect(ban ? NDR_BANNED : NDR_KICKED);
     856             : 
     857             :     // Send message notifying other clients
     858           0 :     CKickedMessage kickedMessage;
     859           0 :     kickedMessage.m_Name = playerName;
     860           0 :     kickedMessage.m_Ban = ban;
     861           0 :     Broadcast(&kickedMessage, { NSS_PREGAME, NSS_JOIN_SYNCING, NSS_INGAME });
     862             : }
     863             : 
     864           0 : void CNetServerWorker::AssignPlayer(int playerID, const CStr& guid)
     865             : {
     866             :     // Remove anyone who's already assigned to this player
     867           0 :     for (std::pair<const CStr, PlayerAssignment>& p : m_PlayerAssignments)
     868             :     {
     869           0 :         if (p.second.m_PlayerID == playerID)
     870           0 :             p.second.m_PlayerID = -1;
     871             :     }
     872             : 
     873             :     // Update this host's assignment if it exists
     874           0 :     if (m_PlayerAssignments.find(guid) != m_PlayerAssignments.end())
     875           0 :         m_PlayerAssignments[guid].m_PlayerID = playerID;
     876             : 
     877           0 :     SendPlayerAssignments();
     878           0 : }
     879             : 
     880           0 : void CNetServerWorker::ConstructPlayerAssignmentMessage(CPlayerAssignmentMessage& message)
     881             : {
     882           0 :     for (const std::pair<const CStr, PlayerAssignment>& p : m_PlayerAssignments)
     883             :     {
     884           0 :         if (!p.second.m_Enabled)
     885           0 :             continue;
     886             : 
     887           0 :         CPlayerAssignmentMessage::S_m_Hosts h;
     888           0 :         h.m_GUID = p.first;
     889           0 :         h.m_Name = p.second.m_Name;
     890           0 :         h.m_PlayerID = p.second.m_PlayerID;
     891           0 :         h.m_Status = p.second.m_Status;
     892           0 :         message.m_Hosts.push_back(h);
     893             :     }
     894           0 : }
     895             : 
     896           0 : void CNetServerWorker::SendPlayerAssignments()
     897             : {
     898           0 :     CPlayerAssignmentMessage message;
     899           0 :     ConstructPlayerAssignmentMessage(message);
     900           0 :     Broadcast(&message, { NSS_PREGAME, NSS_JOIN_SYNCING, NSS_INGAME });
     901           0 : }
     902             : 
     903           0 : const ScriptInterface& CNetServerWorker::GetScriptInterface()
     904             : {
     905           0 :     return *m_ScriptInterface;
     906             : }
     907             : 
     908           0 : void CNetServerWorker::SetTurnLength(u32 msecs)
     909             : {
     910           0 :     if (m_ServerTurnManager)
     911           0 :         m_ServerTurnManager->SetTurnLength(msecs);
     912           0 : }
     913             : 
     914           0 : void CNetServerWorker::ProcessLobbyAuth(const CStr& name, const CStr& token)
     915             : {
     916           0 :     LOGMESSAGE("Net Server: Received lobby auth message from %s with %s", name, token);
     917             :     // Find the user with that guid
     918           0 :     std::vector<CNetServerSession*>::iterator it = std::find_if(m_Sessions.begin(), m_Sessions.end(),
     919           0 :         [&](CNetServerSession* session)
     920           0 :         { return session->GetGUID() == token; });
     921             : 
     922           0 :     if (it == m_Sessions.end())
     923           0 :         return;
     924             : 
     925           0 :     (*it)->SetUserName(name.FromUTF8());
     926             :     // Send an empty message to request the authentication message from the client
     927             :     // after its identity has been confirmed via the lobby
     928           0 :     CAuthenticateMessage emptyMessage;
     929           0 :     (*it)->SendMessage(&emptyMessage);
     930             : }
     931             : 
     932           0 : bool CNetServerWorker::OnClientHandshake(void* context, CFsmEvent* event)
     933             : {
     934           0 :     ENSURE(event->GetType() == (uint)NMT_CLIENT_HANDSHAKE);
     935             : 
     936           0 :     CNetServerSession* session = (CNetServerSession*)context;
     937           0 :     CNetServerWorker& server = session->GetServer();
     938             : 
     939           0 :     CCliHandshakeMessage* message = (CCliHandshakeMessage*)event->GetParamRef();
     940           0 :     if (message->m_ProtocolVersion != PS_PROTOCOL_VERSION)
     941             :     {
     942           0 :         session->Disconnect(NDR_INCORRECT_PROTOCOL_VERSION);
     943           0 :         return false;
     944             :     }
     945             : 
     946           0 :     CStr guid = ps_generate_guid();
     947             :     int count = 0;
     948             :     // Ensure unique GUID
     949           0 :     while(std::find_if(
     950             :         server.m_Sessions.begin(), server.m_Sessions.end(),
     951           0 :         [&guid] (const CNetServerSession* session)
     952           0 :         { return session->GetGUID() == guid; }) != server.m_Sessions.end())
     953             :     {
     954           0 :         if (++count > 100)
     955             :         {
     956           0 :             session->Disconnect(NDR_GUID_FAILED);
     957             :             return true;
     958             :         }
     959           0 :         guid = ps_generate_guid();
     960             :     }
     961             : 
     962           0 :     session->SetGUID(guid);
     963             : 
     964           0 :     CSrvHandshakeResponseMessage handshakeResponse;
     965           0 :     handshakeResponse.m_UseProtocolVersion = PS_PROTOCOL_VERSION;
     966           0 :     handshakeResponse.m_GUID = guid;
     967           0 :     handshakeResponse.m_Flags = 0;
     968             : 
     969           0 :     if (server.m_LobbyAuth)
     970             :     {
     971           0 :         handshakeResponse.m_Flags |= PS_NETWORK_FLAG_REQUIRE_LOBBYAUTH;
     972           0 :         session->SetNextState(NSS_LOBBY_AUTHENTICATE);
     973             :     }
     974             : 
     975           0 :     session->SendMessage(&handshakeResponse);
     976             : 
     977           0 :     return true;
     978             : }
     979             : 
     980           0 : bool CNetServerWorker::OnAuthenticate(void* context, CFsmEvent* event)
     981             : {
     982           0 :     ENSURE(event->GetType() == (uint)NMT_AUTHENTICATE);
     983             : 
     984           0 :     CNetServerSession* session = (CNetServerSession*)context;
     985           0 :     CNetServerWorker& server = session->GetServer();
     986             : 
     987             :     // Prohibit joins while the game is loading
     988           0 :     if (server.m_State == SERVER_STATE_LOADING)
     989             :     {
     990           0 :         LOGMESSAGE("Refused connection while the game is loading");
     991           0 :         session->Disconnect(NDR_SERVER_LOADING);
     992           0 :         return true;
     993             :     }
     994             : 
     995           0 :     CAuthenticateMessage* message = (CAuthenticateMessage*)event->GetParamRef();
     996           0 :     CStrW username = SanitisePlayerName(message->m_Name);
     997           0 :     CStrW usernameWithoutRating(username.substr(0, username.find(L" (")));
     998             : 
     999             :     // Compare the lowercase names as specified by https://xmpp.org/extensions/xep-0029.html#sect-idm139493404168176
    1000             :     // "[...] comparisons will be made in case-normalized canonical form."
    1001           0 :     if (server.m_LobbyAuth && usernameWithoutRating.LowerCase() != session->GetUserName().LowerCase())
    1002             :     {
    1003           0 :         LOGERROR("Net server: lobby auth: %s tried joining as %s",
    1004             :             session->GetUserName().ToUTF8(),
    1005             :             usernameWithoutRating.ToUTF8());
    1006           0 :         session->Disconnect(NDR_LOBBY_AUTH_FAILED);
    1007             :         return true;
    1008             :     }
    1009             : 
    1010             :     // Check the password before anything else.
    1011             :     // NB: m_Name must match the client's salt, @see CNetClient::SetGamePassword
    1012           0 :     if (!server.CheckPassword(message->m_Password, message->m_Name.ToUTF8()))
    1013             :     {
    1014             :         // Noisy logerror because players are not supposed to be able to get the IP,
    1015             :         // so this might be someone targeting the host for some reason
    1016             :         // (or TODO a dedicated server and we do want to log anyways)
    1017           0 :         LOGERROR("Net server: user %s tried joining with the wrong password",
    1018             :                  session->GetUserName().ToUTF8());
    1019           0 :         session->Disconnect(NDR_SERVER_REFUSED);
    1020             :         return true;
    1021             :     }
    1022             : 
    1023             :     // Either deduplicate or prohibit join if name is in use
    1024           0 :     bool duplicatePlayernames = false;
    1025           0 :     CFG_GET_VAL("network.duplicateplayernames", duplicatePlayernames);
    1026             :     // If lobby authentication is enabled, the clients playername has already been registered.
    1027             :     // There also can't be any duplicated names.
    1028           0 :     if (!server.m_LobbyAuth && duplicatePlayernames)
    1029           0 :         username = server.DeduplicatePlayerName(username);
    1030             :     else
    1031             :     {
    1032           0 :         std::vector<CNetServerSession*>::iterator it = std::find_if(
    1033             :             server.m_Sessions.begin(), server.m_Sessions.end(),
    1034           0 :             [&username] (const CNetServerSession* session)
    1035           0 :             { return session->GetUserName() == username; });
    1036             : 
    1037           0 :         if (it != server.m_Sessions.end() && (*it) != session)
    1038             :         {
    1039           0 :             session->Disconnect(NDR_PLAYERNAME_IN_USE);
    1040             :             return true;
    1041             :         }
    1042             :     }
    1043             : 
    1044             :     // Disconnect banned usernames
    1045           0 :     if (std::find(server.m_BannedPlayers.begin(), server.m_BannedPlayers.end(), server.m_LobbyAuth ? usernameWithoutRating : username) != server.m_BannedPlayers.end())
    1046             :     {
    1047           0 :         session->Disconnect(NDR_BANNED);
    1048             :         return true;
    1049             :     }
    1050             : 
    1051           0 :     int maxObservers = 0;
    1052           0 :     CFG_GET_VAL("network.observerlimit", maxObservers);
    1053             : 
    1054           0 :     bool isRejoining = false;
    1055           0 :     bool serverFull = false;
    1056           0 :     if (server.m_State == SERVER_STATE_PREGAME)
    1057             :     {
    1058             :         // Don't check for maxObservers in the gamesetup, as we don't know yet who will be assigned
    1059           0 :         serverFull = server.m_Sessions.size() >= MAX_CLIENTS;
    1060             :     }
    1061             :     else
    1062             :     {
    1063           0 :         bool isObserver = true;
    1064           0 :         int disconnectedPlayers = 0;
    1065           0 :         int connectedPlayers = 0;
    1066             :         // (TODO: if GUIDs were stable, we should use them instead)
    1067           0 :         for (const std::pair<const CStr, PlayerAssignment>& p : server.m_PlayerAssignments)
    1068             :         {
    1069           0 :             const PlayerAssignment& assignment = p.second;
    1070             : 
    1071           0 :             if (!assignment.m_Enabled && assignment.m_Name == username)
    1072             :             {
    1073           0 :                 isObserver = assignment.m_PlayerID == -1;
    1074           0 :                 isRejoining = true;
    1075             :             }
    1076             : 
    1077           0 :             if (assignment.m_PlayerID == -1)
    1078             :                 continue;
    1079             : 
    1080           0 :             if (assignment.m_Enabled)
    1081           0 :                 ++connectedPlayers;
    1082             :             else
    1083           0 :                 ++disconnectedPlayers;
    1084             :         }
    1085             : 
    1086             :         // Optionally allow everyone or only buddies to join after the game has started
    1087           0 :         if (!isRejoining)
    1088             :         {
    1089           0 :             CStr observerLateJoin;
    1090           0 :             CFG_GET_VAL("network.lateobservers", observerLateJoin);
    1091             : 
    1092           0 :             if (observerLateJoin == "everyone")
    1093             :             {
    1094             :                 isRejoining = true;
    1095             :             }
    1096           0 :             else if (observerLateJoin == "buddies")
    1097             :             {
    1098           0 :                 CStr buddies;
    1099           0 :                 CFG_GET_VAL("lobby.buddies", buddies);
    1100           0 :                 std::wstringstream buddiesStream(wstring_from_utf8(buddies));
    1101           0 :                 CStrW buddy;
    1102           0 :                 while (std::getline(buddiesStream, buddy, L','))
    1103             :                 {
    1104           0 :                     if (buddy == usernameWithoutRating)
    1105             :                     {
    1106             :                         isRejoining = true;
    1107             :                         break;
    1108             :                     }
    1109             :                 }
    1110             :             }
    1111             :         }
    1112             : 
    1113           0 :         if (!isRejoining)
    1114             :         {
    1115           0 :             LOGMESSAGE("Refused connection after game start from not-previously-known user \"%s\"", utf8_from_wstring(username));
    1116           0 :             session->Disconnect(NDR_SERVER_ALREADY_IN_GAME);
    1117             :             return true;
    1118             :         }
    1119             : 
    1120             :         // Ensure all players will be able to rejoin
    1121           0 :         serverFull = isObserver && (
    1122           0 :             (int) server.m_Sessions.size() - connectedPlayers > maxObservers ||
    1123           0 :             (int) server.m_Sessions.size() + disconnectedPlayers >= MAX_CLIENTS);
    1124             :     }
    1125             : 
    1126           0 :     if (serverFull)
    1127             :     {
    1128           0 :         session->Disconnect(NDR_SERVER_FULL);
    1129             :         return true;
    1130             :     }
    1131             : 
    1132           0 :     u32 newHostID = server.m_NextHostID++;
    1133             : 
    1134           0 :     session->SetUserName(username);
    1135           0 :     session->SetHostID(newHostID);
    1136             : 
    1137           0 :     CAuthenticateResultMessage authenticateResult;
    1138           0 :     authenticateResult.m_Code = isRejoining ? ARC_OK_REJOINING : ARC_OK;
    1139           0 :     authenticateResult.m_HostID = newHostID;
    1140           0 :     authenticateResult.m_Message = L"Logged in";
    1141           0 :     authenticateResult.m_IsController = 0;
    1142             : 
    1143           0 :     if (message->m_ControllerSecret == server.m_ControllerSecret)
    1144             :     {
    1145           0 :         if (server.m_ControllerGUID.empty())
    1146             :         {
    1147           0 :             server.m_ControllerGUID = session->GetGUID();
    1148           0 :             authenticateResult.m_IsController = 1;
    1149             :         }
    1150             :         // TODO: we could probably handle having several controllers, or swapping?
    1151             :     }
    1152             : 
    1153           0 :     session->SendMessage(&authenticateResult);
    1154             : 
    1155           0 :     server.OnUserJoin(session);
    1156             : 
    1157           0 :     if (isRejoining)
    1158             :     {
    1159           0 :         ENSURE(server.m_State != SERVER_STATE_UNCONNECTED && server.m_State != SERVER_STATE_PREGAME);
    1160             : 
    1161             :         // Request a copy of the current game state from an existing player,
    1162             :         // so we can send it on to the new player
    1163             : 
    1164             :         // Assume session 0 is most likely the local player, so they're
    1165             :         // the most efficient client to request a copy from
    1166           0 :         CNetServerSession* sourceSession = server.m_Sessions.at(0);
    1167             : 
    1168           0 :         sourceSession->GetFileTransferer().StartTask(
    1169           0 :             std::shared_ptr<CNetFileReceiveTask>(new CNetFileReceiveTask_ServerRejoin(server, newHostID))
    1170             :         );
    1171             : 
    1172           0 :         session->SetNextState(NSS_JOIN_SYNCING);
    1173             :     }
    1174             : 
    1175           0 :     return true;
    1176             : }
    1177           0 : bool CNetServerWorker::OnSimulationCommand(void* context, CFsmEvent* event)
    1178             : {
    1179           0 :     ENSURE(event->GetType() == (uint)NMT_SIMULATION_COMMAND);
    1180             : 
    1181           0 :     CNetServerSession* session = (CNetServerSession*)context;
    1182           0 :     CNetServerWorker& server = session->GetServer();
    1183             : 
    1184           0 :     CSimulationMessage* message = (CSimulationMessage*)event->GetParamRef();
    1185             : 
    1186             :     // Ignore messages sent by one player on behalf of another player
    1187             :     // unless cheating is enabled
    1188           0 :     bool cheatsEnabled = false;
    1189           0 :     const ScriptInterface& scriptInterface = server.GetScriptInterface();
    1190           0 :     ScriptRequest rq(scriptInterface);
    1191           0 :     JS::RootedValue settings(rq.cx);
    1192           0 :     Script::GetProperty(rq, server.m_InitAttributes, "settings", &settings);
    1193           0 :     if (Script::HasProperty(rq, settings, "CheatsEnabled"))
    1194           0 :         Script::GetProperty(rq, settings, "CheatsEnabled", cheatsEnabled);
    1195             : 
    1196           0 :     PlayerAssignmentMap::iterator it = server.m_PlayerAssignments.find(session->GetGUID());
    1197             :     // When cheating is disabled, fail if the player the message claims to
    1198             :     // represent does not exist or does not match the sender's player name
    1199           0 :     if (!cheatsEnabled && (it == server.m_PlayerAssignments.end() || it->second.m_PlayerID != message->m_Player))
    1200             :         return true;
    1201             : 
    1202             :     // Send it back to all clients that have finished
    1203             :     // the loading screen (and the synchronization when rejoining)
    1204           0 :     server.Broadcast(message, { NSS_INGAME });
    1205             : 
    1206             :     // Save all the received commands
    1207           0 :     if (server.m_SavedCommands.size() < message->m_Turn + 1)
    1208           0 :         server.m_SavedCommands.resize(message->m_Turn + 1);
    1209           0 :     server.m_SavedCommands[message->m_Turn].push_back(*message);
    1210             : 
    1211             :     // TODO: we shouldn't send the message back to the client that first sent it
    1212             :     return true;
    1213             : }
    1214             : 
    1215           0 : bool CNetServerWorker::OnSyncCheck(void* context, CFsmEvent* event)
    1216             : {
    1217           0 :     ENSURE(event->GetType() == (uint)NMT_SYNC_CHECK);
    1218             : 
    1219           0 :     CNetServerSession* session = (CNetServerSession*)context;
    1220           0 :     CNetServerWorker& server = session->GetServer();
    1221             : 
    1222           0 :     CSyncCheckMessage* message = (CSyncCheckMessage*)event->GetParamRef();
    1223             : 
    1224           0 :     server.m_ServerTurnManager->NotifyFinishedClientUpdate(*session, message->m_Turn, message->m_Hash);
    1225           0 :     return true;
    1226             : }
    1227             : 
    1228           0 : bool CNetServerWorker::OnEndCommandBatch(void* context, CFsmEvent* event)
    1229             : {
    1230           0 :     ENSURE(event->GetType() == (uint)NMT_END_COMMAND_BATCH);
    1231             : 
    1232           0 :     CNetServerSession* session = (CNetServerSession*)context;
    1233           0 :     CNetServerWorker& server = session->GetServer();
    1234             : 
    1235           0 :     CEndCommandBatchMessage* message = (CEndCommandBatchMessage*)event->GetParamRef();
    1236             : 
    1237             :     // The turn-length field is ignored
    1238           0 :     server.m_ServerTurnManager->NotifyFinishedClientCommands(*session, message->m_Turn);
    1239           0 :     return true;
    1240             : }
    1241             : 
    1242           0 : bool CNetServerWorker::OnChat(void* context, CFsmEvent* event)
    1243             : {
    1244           0 :     ENSURE(event->GetType() == (uint)NMT_CHAT);
    1245             : 
    1246           0 :     CNetServerSession* session = (CNetServerSession*)context;
    1247           0 :     CNetServerWorker& server = session->GetServer();
    1248             : 
    1249           0 :     CChatMessage* message = (CChatMessage*)event->GetParamRef();
    1250             : 
    1251           0 :     message->m_GUID = session->GetGUID();
    1252             : 
    1253           0 :     server.Broadcast(message, { NSS_PREGAME, NSS_INGAME });
    1254             : 
    1255           0 :     return true;
    1256             : }
    1257             : 
    1258           0 : bool CNetServerWorker::OnReady(void* context, CFsmEvent* event)
    1259             : {
    1260           0 :     ENSURE(event->GetType() == (uint)NMT_READY);
    1261             : 
    1262           0 :     CNetServerSession* session = (CNetServerSession*)context;
    1263           0 :     CNetServerWorker& server = session->GetServer();
    1264             : 
    1265             :     // Occurs if a client presses not-ready
    1266             :     // in the very last moment before the hosts starts the game
    1267           0 :     if (server.m_State == SERVER_STATE_LOADING)
    1268             :         return true;
    1269             : 
    1270           0 :     CReadyMessage* message = (CReadyMessage*)event->GetParamRef();
    1271           0 :     message->m_GUID = session->GetGUID();
    1272           0 :     server.Broadcast(message, { NSS_PREGAME });
    1273             : 
    1274           0 :     server.m_PlayerAssignments[message->m_GUID].m_Status = message->m_Status;
    1275             : 
    1276           0 :     return true;
    1277             : }
    1278             : 
    1279           0 : bool CNetServerWorker::OnClearAllReady(void* context, CFsmEvent* event)
    1280             : {
    1281           0 :     ENSURE(event->GetType() == (uint)NMT_CLEAR_ALL_READY);
    1282             : 
    1283           0 :     CNetServerSession* session = (CNetServerSession*)context;
    1284           0 :     CNetServerWorker& server = session->GetServer();
    1285             : 
    1286           0 :     if (session->GetGUID() == server.m_ControllerGUID)
    1287           0 :         server.ClearAllPlayerReady();
    1288             : 
    1289           0 :     return true;
    1290             : }
    1291             : 
    1292           0 : bool CNetServerWorker::OnGameSetup(void* context, CFsmEvent* event)
    1293             : {
    1294           0 :     ENSURE(event->GetType() == (uint)NMT_GAME_SETUP);
    1295             : 
    1296           0 :     CNetServerSession* session = (CNetServerSession*)context;
    1297           0 :     CNetServerWorker& server = session->GetServer();
    1298             : 
    1299             :     // Changing the settings after gamestart is not implemented and would cause an Out-of-sync error.
    1300             :     // This happened when doubleclicking on the startgame button.
    1301           0 :     if (server.m_State != SERVER_STATE_PREGAME)
    1302             :         return true;
    1303             : 
    1304             :     // Only the controller is allowed to send game setup updates.
    1305             :     // TODO: it would be good to allow other players to request changes to some settings,
    1306             :     // e.g. their civilisation.
    1307             :     // Possibly this should use another message, to enforce a single source of truth.
    1308           0 :     if (session->GetGUID() == server.m_ControllerGUID)
    1309             :     {
    1310           0 :         CGameSetupMessage* message = (CGameSetupMessage*)event->GetParamRef();
    1311           0 :         server.Broadcast(message, { NSS_PREGAME });
    1312             :     }
    1313             :     return true;
    1314             : }
    1315             : 
    1316           0 : bool CNetServerWorker::OnAssignPlayer(void* context, CFsmEvent* event)
    1317             : {
    1318           0 :     ENSURE(event->GetType() == (uint)NMT_ASSIGN_PLAYER);
    1319           0 :     CNetServerSession* session = (CNetServerSession*)context;
    1320           0 :     CNetServerWorker& server = session->GetServer();
    1321             : 
    1322           0 :     if (session->GetGUID() == server.m_ControllerGUID)
    1323             :     {
    1324           0 :         CAssignPlayerMessage* message = (CAssignPlayerMessage*)event->GetParamRef();
    1325           0 :         server.AssignPlayer(message->m_PlayerID, message->m_GUID);
    1326             :     }
    1327           0 :     return true;
    1328             : }
    1329             : 
    1330           0 : bool CNetServerWorker::OnGameStart(void* context, CFsmEvent* event)
    1331             : {
    1332           0 :     ENSURE(event->GetType() == (uint)NMT_GAME_START);
    1333           0 :     CNetServerSession* session = (CNetServerSession*)context;
    1334           0 :     CNetServerWorker& server = session->GetServer();
    1335             : 
    1336           0 :     if (session->GetGUID() != server.m_ControllerGUID)
    1337             :         return true;
    1338             : 
    1339           0 :     CGameStartMessage* message = (CGameStartMessage*)event->GetParamRef();
    1340           0 :     server.StartGame(message->m_InitAttributes);
    1341           0 :     return true;
    1342             : }
    1343             : 
    1344           0 : bool CNetServerWorker::OnLoadedGame(void* context, CFsmEvent* event)
    1345             : {
    1346           0 :     ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);
    1347             : 
    1348           0 :     CNetServerSession* loadedSession = (CNetServerSession*)context;
    1349           0 :     CNetServerWorker& server = loadedSession->GetServer();
    1350             : 
    1351             :     // We're in the loading state, so wait until every client has loaded
    1352             :     // before starting the game
    1353           0 :     ENSURE(server.m_State == SERVER_STATE_LOADING);
    1354           0 :     if (server.CheckGameLoadStatus(loadedSession))
    1355             :         return true;
    1356             : 
    1357           0 :     CClientsLoadingMessage message;
    1358             :     // We always send all GUIDs of clients in the loading state
    1359             :     // so that we don't have to bother about switching GUI pages
    1360           0 :     for (CNetServerSession* session : server.m_Sessions)
    1361           0 :         if (session->GetCurrState() != NSS_INGAME && loadedSession->GetGUID() != session->GetGUID())
    1362             :         {
    1363           0 :             CClientsLoadingMessage::S_m_Clients client;
    1364           0 :             client.m_GUID = session->GetGUID();
    1365           0 :             message.m_Clients.push_back(client);
    1366             :         }
    1367             : 
    1368             :     // Send to the client who has loaded the game but did not reach the NSS_INGAME state yet
    1369           0 :     loadedSession->SendMessage(&message);
    1370           0 :     server.Broadcast(&message, { NSS_INGAME });
    1371             : 
    1372           0 :     return true;
    1373             : }
    1374             : 
    1375           0 : bool CNetServerWorker::OnJoinSyncingLoadedGame(void* context, CFsmEvent* event)
    1376             : {
    1377             :     // A client rejoining an in-progress game has now finished loading the
    1378             :     // map and deserialized the initial state.
    1379             :     // The simulation may have progressed since then, so send any subsequent
    1380             :     // commands to them and set them as an active player so they can participate
    1381             :     // in all future turns.
    1382             :     //
    1383             :     // (TODO: if it takes a long time for them to receive and execute all these
    1384             :     // commands, the other players will get frozen for that time and may be unhappy;
    1385             :     // we could try repeating this process a few times until the client converges
    1386             :     // on the up-to-date state, before setting them as active.)
    1387             : 
    1388           0 :     ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);
    1389             : 
    1390           0 :     CNetServerSession* session = (CNetServerSession*)context;
    1391           0 :     CNetServerWorker& server = session->GetServer();
    1392             : 
    1393           0 :     CLoadedGameMessage* message = (CLoadedGameMessage*)event->GetParamRef();
    1394             : 
    1395           0 :     u32 turn = message->m_CurrentTurn;
    1396           0 :     u32 readyTurn = server.m_ServerTurnManager->GetReadyTurn();
    1397             : 
    1398             :     // Send them all commands received since their saved state,
    1399             :     // and turn-ended messages for any turns that have already been processed
    1400           0 :     for (size_t i = turn + 1; i < std::max(readyTurn+1, (u32)server.m_SavedCommands.size()); ++i)
    1401             :     {
    1402           0 :         if (i < server.m_SavedCommands.size())
    1403           0 :             for (size_t j = 0; j < server.m_SavedCommands[i].size(); ++j)
    1404           0 :                 session->SendMessage(&server.m_SavedCommands[i][j]);
    1405             : 
    1406           0 :         if (i <= readyTurn)
    1407             :         {
    1408           0 :             CEndCommandBatchMessage endMessage;
    1409           0 :             endMessage.m_Turn = i;
    1410           0 :             endMessage.m_TurnLength = server.m_ServerTurnManager->GetSavedTurnLength(i);
    1411           0 :             session->SendMessage(&endMessage);
    1412             :         }
    1413             :     }
    1414             : 
    1415             :     // Tell the turn manager to expect commands from this new client
    1416             :     // Special case: the controller shouldn't be treated as an observer in any case.
    1417           0 :     bool isObserver = server.m_PlayerAssignments[session->GetGUID()].m_PlayerID == -1 && server.m_ControllerGUID != session->GetGUID();
    1418           0 :     server.m_ServerTurnManager->InitialiseClient(session->GetHostID(), readyTurn, isObserver);
    1419             : 
    1420             :     // Tell the client that everything has finished loading and it should start now
    1421           0 :     CLoadedGameMessage loaded;
    1422           0 :     loaded.m_CurrentTurn = readyTurn;
    1423           0 :     session->SendMessage(&loaded);
    1424             : 
    1425           0 :     return true;
    1426             : }
    1427             : 
    1428           0 : bool CNetServerWorker::OnRejoined(void* context, CFsmEvent* event)
    1429             : {
    1430             :     // A client has finished rejoining and the loading screen disappeared.
    1431           0 :     ENSURE(event->GetType() == (uint)NMT_REJOINED);
    1432             : 
    1433           0 :     CNetServerSession* session = (CNetServerSession*)context;
    1434           0 :     CNetServerWorker& server = session->GetServer();
    1435             : 
    1436             :     // Inform everyone of the client having rejoined
    1437           0 :     CRejoinedMessage* message = (CRejoinedMessage*)event->GetParamRef();
    1438           0 :     message->m_GUID = session->GetGUID();
    1439           0 :     server.Broadcast(message, { NSS_INGAME });
    1440             : 
    1441             :     // Send all pausing players to the rejoined client.
    1442           0 :     for (const CStr& guid : server.m_PausingPlayers)
    1443             :     {
    1444           0 :         CClientPausedMessage pausedMessage;
    1445           0 :         pausedMessage.m_GUID = guid;
    1446           0 :         pausedMessage.m_Pause = true;
    1447           0 :         session->SendMessage(&pausedMessage);
    1448             :     }
    1449             : 
    1450           0 :     return true;
    1451             : }
    1452             : 
    1453           0 : bool CNetServerWorker::OnKickPlayer(void* context, CFsmEvent* event)
    1454             : {
    1455           0 :     ENSURE(event->GetType() == (uint)NMT_KICKED);
    1456             : 
    1457           0 :     CNetServerSession* session = (CNetServerSession*)context;
    1458           0 :     CNetServerWorker& server = session->GetServer();
    1459             : 
    1460           0 :     if (session->GetGUID() == server.m_ControllerGUID)
    1461             :     {
    1462           0 :         CKickedMessage* message = (CKickedMessage*)event->GetParamRef();
    1463           0 :         server.KickPlayer(message->m_Name, message->m_Ban);
    1464             :     }
    1465           0 :     return true;
    1466             : }
    1467             : 
    1468           0 : bool CNetServerWorker::OnDisconnect(void* context, CFsmEvent* event)
    1469             : {
    1470           0 :     ENSURE(event->GetType() == (uint)NMT_CONNECTION_LOST);
    1471             : 
    1472           0 :     CNetServerSession* session = (CNetServerSession*)context;
    1473           0 :     CNetServerWorker& server = session->GetServer();
    1474             : 
    1475           0 :     server.OnUserLeave(session);
    1476             : 
    1477           0 :     return true;
    1478             : }
    1479             : 
    1480           0 : bool CNetServerWorker::OnClientPaused(void* context, CFsmEvent* event)
    1481             : {
    1482           0 :     ENSURE(event->GetType() == (uint)NMT_CLIENT_PAUSED);
    1483             : 
    1484           0 :     CNetServerSession* session = (CNetServerSession*)context;
    1485           0 :     CNetServerWorker& server = session->GetServer();
    1486             : 
    1487           0 :     CClientPausedMessage* message = (CClientPausedMessage*)event->GetParamRef();
    1488             : 
    1489           0 :     message->m_GUID = session->GetGUID();
    1490             : 
    1491             :     // Update the list of pausing players.
    1492           0 :     std::vector<CStr>::iterator player = std::find(server.m_PausingPlayers.begin(), server.m_PausingPlayers.end(), session->GetGUID());
    1493             : 
    1494           0 :     if (message->m_Pause)
    1495             :     {
    1496           0 :         if (player != server.m_PausingPlayers.end())
    1497             :             return true;
    1498             : 
    1499           0 :         server.m_PausingPlayers.push_back(session->GetGUID());
    1500             :     }
    1501             :     else
    1502             :     {
    1503           0 :         if (player == server.m_PausingPlayers.end())
    1504             :             return true;
    1505             : 
    1506           0 :         server.m_PausingPlayers.erase(player);
    1507             :     }
    1508             : 
    1509             :     // Send messages to clients that are in game, and are not the client who paused.
    1510           0 :     for (CNetServerSession* netSession : server.m_Sessions)
    1511           0 :         if (netSession->GetCurrState() == NSS_INGAME && message->m_GUID != netSession->GetGUID())
    1512           0 :             netSession->SendMessage(message);
    1513             : 
    1514             :     return true;
    1515             : }
    1516             : 
    1517           0 : bool CNetServerWorker::CheckGameLoadStatus(CNetServerSession* changedSession)
    1518             : {
    1519           0 :     for (const CNetServerSession* session : m_Sessions)
    1520           0 :         if (session != changedSession && session->GetCurrState() != NSS_INGAME)
    1521             :             return false;
    1522             : 
    1523             :     // Inform clients that everyone has loaded the map and that the game can start
    1524           0 :     CLoadedGameMessage loaded;
    1525           0 :     loaded.m_CurrentTurn = 0;
    1526             : 
    1527             :     // Notice the changedSession is still in the NSS_PREGAME state
    1528           0 :     Broadcast(&loaded, { NSS_PREGAME, NSS_INGAME });
    1529             : 
    1530           0 :     m_State = SERVER_STATE_INGAME;
    1531           0 :     return true;
    1532             : }
    1533             : 
    1534           0 : void CNetServerWorker::StartGame(const CStr& initAttribs)
    1535             : {
    1536           0 :     for (std::pair<const CStr, PlayerAssignment>& player : m_PlayerAssignments)
    1537           0 :         if (player.second.m_Enabled && player.second.m_PlayerID != -1 && player.second.m_Status == 0)
    1538             :         {
    1539           0 :             LOGERROR("Tried to start the game without player \"%s\" being ready!", utf8_from_wstring(player.second.m_Name).c_str());
    1540           0 :             return;
    1541             :         }
    1542             : 
    1543           0 :     m_ServerTurnManager = new CNetServerTurnManager(*this);
    1544             : 
    1545           0 :     for (CNetServerSession* session : m_Sessions)
    1546             :     {
    1547             :         // Special case: the controller shouldn't be treated as an observer in any case.
    1548           0 :         bool isObserver = m_PlayerAssignments[session->GetGUID()].m_PlayerID == -1 && m_ControllerGUID != session->GetGUID();
    1549           0 :         m_ServerTurnManager->InitialiseClient(session->GetHostID(), 0, isObserver);
    1550             :     }
    1551             : 
    1552           0 :     m_State = SERVER_STATE_LOADING;
    1553             : 
    1554             :     // Remove players and observers that are not present when the game starts
    1555           0 :     for (PlayerAssignmentMap::iterator it = m_PlayerAssignments.begin(); it != m_PlayerAssignments.end();)
    1556           0 :         if (it->second.m_Enabled)
    1557           0 :             ++it;
    1558             :         else
    1559           0 :             it = m_PlayerAssignments.erase(it);
    1560             : 
    1561           0 :     SendPlayerAssignments();
    1562             : 
    1563             :     // Update init attributes. They should no longer change.
    1564           0 :     Script::ParseJSON(ScriptRequest(m_ScriptInterface), initAttribs, &m_InitAttributes);
    1565             : 
    1566           0 :     CGameStartMessage gameStart;
    1567           0 :     gameStart.m_InitAttributes = initAttribs;
    1568           0 :     Broadcast(&gameStart, { NSS_PREGAME });
    1569             : }
    1570             : 
    1571           0 : CStrW CNetServerWorker::SanitisePlayerName(const CStrW& original)
    1572             : {
    1573           0 :     const size_t MAX_LENGTH = 32;
    1574             : 
    1575           0 :     CStrW name = original;
    1576           0 :     name.Replace(L"[", L"{"); // remove GUI tags
    1577           0 :     name.Replace(L"]", L"}"); // remove for symmetry
    1578             : 
    1579             :     // Restrict the length
    1580           0 :     if (name.length() > MAX_LENGTH)
    1581           0 :         name = name.Left(MAX_LENGTH);
    1582             : 
    1583             :     // Don't allow surrounding whitespace
    1584           0 :     name.Trim(PS_TRIM_BOTH);
    1585             : 
    1586             :     // Don't allow empty name
    1587           0 :     if (name.empty())
    1588           0 :         name = L"Anonymous";
    1589             : 
    1590           0 :     return name;
    1591             : }
    1592             : 
    1593           0 : CStrW CNetServerWorker::DeduplicatePlayerName(const CStrW& original)
    1594             : {
    1595           0 :     CStrW name = original;
    1596             : 
    1597             :     // Try names "Foo", "Foo (2)", "Foo (3)", etc
    1598           0 :     size_t id = 2;
    1599           0 :     while (true)
    1600             :     {
    1601           0 :         bool unique = true;
    1602           0 :         for (const CNetServerSession* session : m_Sessions)
    1603             :         {
    1604           0 :             if (session->GetUserName() == name)
    1605             :             {
    1606             :                 unique = false;
    1607             :                 break;
    1608             :             }
    1609             :         }
    1610             : 
    1611           0 :         if (unique)
    1612           0 :             return name;
    1613             : 
    1614           0 :         name = original + L" (" + CStrW::FromUInt(id++) + L")";
    1615           0 :     }
    1616             : }
    1617             : 
    1618           0 : void CNetServerWorker::SendHolePunchingMessage(const CStr& ipStr, u16 port)
    1619             : {
    1620           0 :     if (m_Host)
    1621           0 :         StunClient::SendHolePunchingMessages(*m_Host, ipStr, port);
    1622           0 : }
    1623             : 
    1624             : 
    1625             : 
    1626             : 
    1627           0 : CNetServer::CNetServer(bool useLobbyAuth, int autostartPlayers) :
    1628           0 :     m_Worker(new CNetServerWorker(useLobbyAuth, autostartPlayers)),
    1629           0 :     m_LobbyAuth(useLobbyAuth), m_UseSTUN(false), m_PublicIp(""), m_PublicPort(20595), m_Password()
    1630             : {
    1631           0 : }
    1632             : 
    1633           0 : CNetServer::~CNetServer()
    1634             : {
    1635           0 :     delete m_Worker;
    1636           0 : }
    1637             : 
    1638           0 : bool CNetServer::GetUseSTUN() const
    1639             : {
    1640           0 :     return m_UseSTUN;
    1641             : }
    1642             : 
    1643           0 : bool CNetServer::UseLobbyAuth() const
    1644             : {
    1645           0 :     return m_LobbyAuth;
    1646             : }
    1647             : 
    1648           0 : bool CNetServer::SetupConnection(const u16 port)
    1649             : {
    1650           0 :     return m_Worker->SetupConnection(port);
    1651             : }
    1652             : 
    1653           0 : CStr CNetServer::GetPublicIp() const
    1654             : {
    1655           0 :     return m_PublicIp;
    1656             : }
    1657             : 
    1658           0 : u16 CNetServer::GetPublicPort() const
    1659             : {
    1660           0 :     return m_PublicPort;
    1661             : }
    1662             : 
    1663           0 : u16 CNetServer::GetLocalPort() const
    1664             : {
    1665           0 :     std::lock_guard<std::mutex> lock(m_Worker->m_WorkerMutex);
    1666           0 :     if (!m_Worker->m_Host)
    1667             :         return 0;
    1668           0 :     return m_Worker->m_Host->address.port;
    1669             : }
    1670             : 
    1671           0 : void CNetServer::SetConnectionData(const CStr& ip, const u16 port)
    1672             : {
    1673           0 :     m_PublicIp = ip;
    1674           0 :     m_PublicPort = port;
    1675           0 :     m_UseSTUN = false;
    1676           0 : }
    1677             : 
    1678           0 : bool CNetServer::SetConnectionDataViaSTUN()
    1679             : {
    1680           0 :     m_UseSTUN = true;
    1681           0 :     std::lock_guard<std::mutex> lock(m_Worker->m_WorkerMutex);
    1682           0 :     if (!m_Worker->m_Host)
    1683             :         return false;
    1684           0 :     return StunClient::FindPublicIP(*m_Worker->m_Host, m_PublicIp, m_PublicPort);
    1685             : }
    1686             : 
    1687           0 : bool CNetServer::CheckPasswordAndIncrement(const std::string& username, const std::string& password, const std::string& salt)
    1688             : {
    1689           0 :     std::unordered_map<std::string, int>::iterator it = m_FailedAttempts.find(username);
    1690           0 :     if (m_Worker->CheckPassword(password, salt))
    1691             :     {
    1692           0 :         if (it != m_FailedAttempts.end())
    1693           0 :             it->second = 0;
    1694           0 :         return true;
    1695             :     }
    1696           0 :     if (it == m_FailedAttempts.end())
    1697           0 :         m_FailedAttempts.emplace(username, 1);
    1698             :     else
    1699           0 :         it->second++;
    1700             :     return false;
    1701             : }
    1702             : 
    1703           0 : bool CNetServer::IsBanned(const std::string& username) const
    1704             : {
    1705           0 :     std::unordered_map<std::string, int>::const_iterator it = m_FailedAttempts.find(username);
    1706           0 :     return it != m_FailedAttempts.end() && it->second >= FAILED_PASSWORD_TRIES_BEFORE_BAN;
    1707             : }
    1708             : 
    1709           0 : void CNetServer::SetPassword(const CStr& password)
    1710             : {
    1711           0 :     m_Password = password;
    1712           0 :     std::lock_guard<std::mutex> lock(m_Worker->m_WorkerMutex);
    1713           0 :     m_Worker->SetPassword(password);
    1714           0 : }
    1715             : 
    1716           0 : void CNetServer::SetControllerSecret(const std::string& secret)
    1717             : {
    1718           0 :     std::lock_guard<std::mutex> lock(m_Worker->m_WorkerMutex);
    1719           0 :     m_Worker->SetControllerSecret(secret);
    1720           0 : }
    1721             : 
    1722           0 : void CNetServer::StartGame()
    1723             : {
    1724           0 :     std::lock_guard<std::mutex> lock(m_Worker->m_WorkerMutex);
    1725           0 :     m_Worker->m_StartGameQueue.push_back(true);
    1726           0 : }
    1727             : 
    1728           0 : void CNetServer::UpdateInitAttributes(JS::MutableHandleValue attrs, const ScriptRequest& rq)
    1729             : {
    1730             :     // Pass the attributes as JSON, since that's the easiest safe
    1731             :     // cross-thread way of passing script data
    1732           0 :     std::string attrsJSON = Script::StringifyJSON(rq, attrs, false);
    1733             : 
    1734           0 :     std::lock_guard<std::mutex> lock(m_Worker->m_WorkerMutex);
    1735           0 :     m_Worker->m_InitAttributesQueue.push_back(attrsJSON);
    1736           0 : }
    1737             : 
    1738           0 : void CNetServer::OnLobbyAuth(const CStr& name, const CStr& token)
    1739             : {
    1740           0 :     std::lock_guard<std::mutex> lock(m_Worker->m_WorkerMutex);
    1741           0 :     m_Worker->m_LobbyAuthQueue.push_back(std::make_pair(name, token));
    1742           0 : }
    1743             : 
    1744           0 : void CNetServer::SetTurnLength(u32 msecs)
    1745             : {
    1746           0 :     std::lock_guard<std::mutex> lock(m_Worker->m_WorkerMutex);
    1747           0 :     m_Worker->m_TurnLengthQueue.push_back(msecs);
    1748           0 : }
    1749             : 
    1750           0 : void CNetServer::SendHolePunchingMessage(const CStr& ip, u16 port)
    1751             : {
    1752           0 :     m_Worker->SendHolePunchingMessage(ip, port);
    1753           0 : }

Generated by: LCOV version 1.13