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