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 835 0.0 %
Date: 2023-01-19 00:18:29 Functions: 0 79 0.0 %

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

Generated by: LCOV version 1.13