| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843 | 
							- /*
 
-  * LobbyServer.cpp, part of VCMI engine
 
-  *
 
-  * Authors: listed in file AUTHORS in main folder
 
-  *
 
-  * License: GNU General Public License v2.0 or later
 
-  * Full text of license available in license.txt file, in main folder
 
-  *
 
-  */
 
- #include "StdInc.h"
 
- #include "LobbyServer.h"
 
- #include "LobbyDatabase.h"
 
- #include "../lib/json/JsonFormatException.h"
 
- #include "../lib/json/JsonNode.h"
 
- #include "../lib/json/JsonUtils.h"
 
- #include "../lib/texts/Languages.h"
 
- #include "../lib/texts/TextOperations.h"
 
- #include <boost/uuid/uuid_generators.hpp>
 
- #include <boost/uuid/uuid_io.hpp>
 
- bool LobbyServer::isAccountNameValid(const std::string & accountName) const
 
- {
 
- 	// Arbitrary limit on account name length.
 
- 	// Can be extended if there are no issues with UI space
 
- 	if(accountName.size() < 4)
 
- 		return false;
 
- 	if(accountName.size() > 20)
 
- 		return false;
 
- 	// For now permit only latin alphabet and numbers
 
- 	// Can be extended, but makes sure that such symbols will be present in all H3 fonts
 
- 	for(const auto & c : accountName)
 
- 		if(!std::isalnum(c))
 
- 			return false;
 
- 	return true;
 
- }
 
- std::string LobbyServer::sanitizeChatMessage(const std::string & inputString) const
 
- {
 
- 	static const std::string blacklist = "{}";
 
- 	std::string sanitized;
 
- 	for(const auto & ch : inputString)
 
- 	{
 
- 		// Remove all control characters
 
- 		if (ch >= '\0' && ch < ' ')
 
- 			continue;
 
- 		// Remove blacklisted characters such as brackets that are used for text formatting
 
- 		if (blacklist.find(ch) != std::string::npos)
 
- 			continue;
 
- 		sanitized += ch;
 
- 	}
 
- 	return boost::trim_copy(sanitized);
 
- }
 
- NetworkConnectionPtr LobbyServer::findAccount(const std::string & accountID) const
 
- {
 
- 	for(const auto & account : activeAccounts)
 
- 		if(account.second == accountID)
 
- 			return account.first;
 
- 	return nullptr;
 
- }
 
- NetworkConnectionPtr LobbyServer::findGameRoom(const std::string & gameRoomID) const
 
- {
 
- 	for(const auto & account : activeGameRooms)
 
- 		if(account.second == gameRoomID)
 
- 			return account.first;
 
- 	return nullptr;
 
- }
 
- void LobbyServer::sendMessage(const NetworkConnectionPtr & target, const JsonNode & json)
 
- {
 
- 	logGlobal->info("Sending message of type %s", json["type"].String());
 
- 	assert(JsonUtils::validate(json, "vcmi:lobbyProtocol/" + json["type"].String(), json["type"].String() + " pack"));
 
- 	target->sendPacket(json.toBytes());
 
- }
 
- void LobbyServer::sendAccountCreated(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & accountCookie)
 
- {
 
- 	JsonNode reply;
 
- 	reply["type"].String() = "accountCreated";
 
- 	reply["accountID"].String() = accountID;
 
- 	reply["accountCookie"].String() = accountCookie;
 
- 	sendMessage(target, reply);
 
- }
 
- void LobbyServer::sendInviteReceived(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & gameRoomID)
 
- {
 
- 	JsonNode reply;
 
- 	reply["type"].String() = "inviteReceived";
 
- 	reply["accountID"].String() = accountID;
 
- 	reply["gameRoomID"].String() = gameRoomID;
 
- 	sendMessage(target, reply);
 
- }
 
- void LobbyServer::sendOperationFailed(const NetworkConnectionPtr & target, const std::string & reason)
 
- {
 
- 	JsonNode reply;
 
- 	reply["type"].String() = "operationFailed";
 
- 	reply["reason"].String() = reason;
 
- 	sendMessage(target, reply);
 
- }
 
- void LobbyServer::sendClientLoginSuccess(const NetworkConnectionPtr & target, const std::string & accountCookie, const std::string & displayName)
 
- {
 
- 	JsonNode reply;
 
- 	reply["type"].String() = "clientLoginSuccess";
 
- 	reply["accountCookie"].String() = accountCookie;
 
- 	reply["displayName"].String() = displayName;
 
- 	sendMessage(target, reply);
 
- }
 
- void LobbyServer::sendServerLoginSuccess(const NetworkConnectionPtr & target, const std::string & accountCookie)
 
- {
 
- 	JsonNode reply;
 
- 	reply["type"].String() = "serverLoginSuccess";
 
- 	reply["accountCookie"].String() = accountCookie;
 
- 	sendMessage(target, reply);
 
- }
 
- void LobbyServer::sendFullChatHistory(const NetworkConnectionPtr & target, const std::string & channelType, const std::string & channelName, const std::string & channelNameForClient)
 
- {
 
- 	sendChatHistory(target, channelType, channelNameForClient, database->getFullMessageHistory(channelType, channelName));
 
- }
 
- void LobbyServer::sendRecentChatHistory(const NetworkConnectionPtr & target, const std::string & channelType, const std::string & channelName)
 
- {
 
- 	sendChatHistory(target, channelType, channelName, database->getRecentMessageHistory(channelType, channelName));
 
- }
 
- void LobbyServer::sendChatHistory(const NetworkConnectionPtr & target, const std::string & channelType, const std::string & channelName, const std::vector<LobbyChatMessage> & history)
 
- {
 
- 	JsonNode reply;
 
- 	reply["type"].String() = "chatHistory";
 
- 	reply["channelType"].String() = channelType;
 
- 	reply["channelName"].String() = channelName;
 
- 	reply["messages"].Vector(); // force creation of empty vector
 
- 	for(const auto & message : boost::adaptors::reverse(history))
 
- 	{
 
- 		JsonNode jsonEntry;
 
- 		jsonEntry["accountID"].String() = message.accountID;
 
- 		jsonEntry["displayName"].String() = message.displayName;
 
- 		jsonEntry["messageText"].String() = message.messageText;
 
- 		jsonEntry["ageSeconds"].Integer() = message.age.count();
 
- 		reply["messages"].Vector().push_back(jsonEntry);
 
- 	}
 
- 	sendMessage(target, reply);
 
- }
 
- void LobbyServer::broadcastActiveAccounts()
 
- {
 
- 	auto activeAccountsStats = database->getActiveAccounts();
 
- 	JsonNode reply;
 
- 	reply["type"].String() = "activeAccounts";
 
- 	reply["accounts"].Vector(); // force creation of empty vector
 
- 	for(const auto & account : activeAccountsStats)
 
- 	{
 
- 		JsonNode jsonEntry;
 
- 		jsonEntry["accountID"].String() = account.accountID;
 
- 		jsonEntry["displayName"].String() = account.displayName;
 
- 		jsonEntry["status"].String() = "In Lobby"; // TODO: in room status, in match status, offline status(?)
 
- 		reply["accounts"].Vector().push_back(jsonEntry);
 
- 	}
 
- 	for(const auto & connection : activeAccounts)
 
- 		sendMessage(connection.first, reply);
 
- }
 
- static JsonNode loadLobbyAccountToJson(const LobbyAccount & account)
 
- {
 
- 	JsonNode jsonEntry;
 
- 	jsonEntry["accountID"].String() = account.accountID;
 
- 	jsonEntry["displayName"].String() = account.displayName;
 
- 	return jsonEntry;
 
- }
 
- static JsonNode loadLobbyGameRoomToJson(const LobbyGameRoom & gameRoom)
 
- {
 
- 	static constexpr std::array LOBBY_ROOM_STATE_NAMES = {
 
- 		"idle",
 
- 		"public",
 
- 		"private",
 
- 		"busy",
 
- 		"cancelled",
 
- 		"closed"
 
- 	};
 
- 	JsonNode jsonEntry;
 
- 	jsonEntry["gameRoomID"].String() = gameRoom.roomID;
 
- 	jsonEntry["hostAccountID"].String() = gameRoom.hostAccountID;
 
- 	jsonEntry["hostAccountDisplayName"].String() = gameRoom.hostAccountDisplayName;
 
- 	jsonEntry["description"].String() = gameRoom.description;
 
- 	jsonEntry["version"].String() = gameRoom.version;
 
- 	jsonEntry["status"].String() = LOBBY_ROOM_STATE_NAMES[vstd::to_underlying(gameRoom.roomState)];
 
- 	jsonEntry["playerLimit"].Integer() = gameRoom.playerLimit;
 
- 	jsonEntry["ageSeconds"].Integer() = gameRoom.age.count();
 
- 	if (!gameRoom.modsJson.empty()) // not present in match history
 
- 		jsonEntry["mods"] = JsonNode(reinterpret_cast<const std::byte *>(gameRoom.modsJson.data()), gameRoom.modsJson.size(), "<lobby "+gameRoom.roomID+">");
 
- 	for(const auto & account : gameRoom.participants)
 
- 		jsonEntry["participants"].Vector().push_back(loadLobbyAccountToJson(account));
 
- 	for(const auto & account : gameRoom.invited)
 
- 		jsonEntry["invited"].Vector().push_back(loadLobbyAccountToJson(account));
 
- 	return jsonEntry;
 
- }
 
- void LobbyServer::sendMatchesHistory(const NetworkConnectionPtr & target)
 
- {
 
- 	std::string accountID = activeAccounts.at(target);
 
- 	auto matchesHistory = database->getAccountGameHistory(accountID);
 
- 	JsonNode reply;
 
- 	reply["type"].String() = "matchesHistory";
 
- 	reply["matchesHistory"].Vector(); // force creation of empty vector
 
- 	for(const auto & gameRoom : matchesHistory)
 
- 		reply["matchesHistory"].Vector().push_back(loadLobbyGameRoomToJson(gameRoom));
 
- 	sendMessage(target, reply);
 
- }
 
- JsonNode LobbyServer::prepareActiveGameRooms()
 
- {
 
- 	auto activeGameRoomStats = database->getActiveGameRooms();
 
- 	JsonNode reply;
 
- 	reply["type"].String() = "activeGameRooms";
 
- 	reply["gameRooms"].Vector(); // force creation of empty vector
 
- 	for(const auto & gameRoom : activeGameRoomStats)
 
- 		reply["gameRooms"].Vector().push_back(loadLobbyGameRoomToJson(gameRoom));
 
- 	return reply;
 
- }
 
- void LobbyServer::broadcastActiveGameRooms()
 
- {
 
- 	auto reply = prepareActiveGameRooms();
 
- 	for(const auto & connection : activeAccounts)
 
- 		sendMessage(connection.first, reply);
 
- }
 
- void LobbyServer::sendAccountJoinsRoom(const NetworkConnectionPtr & target, const std::string & accountID)
 
- {
 
- 	JsonNode reply;
 
- 	reply["type"].String() = "accountJoinsRoom";
 
- 	reply["accountID"].String() = accountID;
 
- 	sendMessage(target, reply);
 
- }
 
- void LobbyServer::sendJoinRoomSuccess(const NetworkConnectionPtr & target, const std::string & gameRoomID, bool proxyMode)
 
- {
 
- 	JsonNode reply;
 
- 	reply["type"].String() = "joinRoomSuccess";
 
- 	reply["gameRoomID"].String() = gameRoomID;
 
- 	reply["proxyMode"].Bool() = proxyMode;
 
- 	sendMessage(target, reply);
 
- }
 
- void LobbyServer::sendChatMessage(const NetworkConnectionPtr & target, const std::string & channelType, const std::string & channelName, const std::string & accountID, const std::string & displayName, const std::string & messageText)
 
- {
 
- 	JsonNode reply;
 
- 	reply["type"].String() = "chatMessage";
 
- 	reply["messageText"].String() = messageText;
 
- 	reply["accountID"].String() = accountID;
 
- 	reply["displayName"].String() = displayName;
 
- 	reply["channelType"].String() = channelType;
 
- 	reply["channelName"].String() = channelName;
 
- 	sendMessage(target, reply);
 
- }
 
- void LobbyServer::onNewConnection(const NetworkConnectionPtr & connection)
 
- {
 
- 	connection->setAsyncWritesEnabled(true);
 
- 	// no-op - waiting for incoming data
 
- }
 
- void LobbyServer::onDisconnected(const NetworkConnectionPtr & connection, const std::string & errorMessage)
 
- {
 
- 	if(activeAccounts.count(connection))
 
- 	{
 
- 		logGlobal->info("Account %s disconnecting. Accounts online: %d", activeAccounts.at(connection), activeAccounts.size() - 1);
 
- 		database->setAccountOnline(activeAccounts.at(connection), false);
 
- 		activeAccounts.erase(connection);
 
- 	}
 
- 	if(activeGameRooms.count(connection))
 
- 	{
 
- 		std::string gameRoomID = activeGameRooms.at(connection);
 
- 		logGlobal->info("Game room %s disconnecting. Rooms online: %d", gameRoomID, activeGameRooms.size() - 1);
 
- 		if (database->getGameRoomStatus(gameRoomID) == LobbyRoomState::BUSY)
 
- 		{
 
- 			database->setGameRoomStatus(gameRoomID, LobbyRoomState::CLOSED);
 
- 			for(const auto & accountConnection : activeAccounts)
 
- 				if (database->isPlayerInGameRoom(accountConnection.second, gameRoomID))
 
- 					sendMatchesHistory(accountConnection.first);
 
- 		}
 
- 		else
 
- 			database->setGameRoomStatus(gameRoomID, LobbyRoomState::CANCELLED);
 
- 		activeGameRooms.erase(connection);
 
- 	}
 
- 	if(activeProxies.count(connection))
 
- 	{
 
- 		const auto & otherConnection = activeProxies.at(connection);
 
- 		if (otherConnection)
 
- 			otherConnection->close();
 
- 		activeProxies.erase(connection);
 
- 		activeProxies.erase(otherConnection);
 
- 	}
 
- 	broadcastActiveAccounts();
 
- 	broadcastActiveGameRooms();
 
- }
 
- JsonNode LobbyServer::parseAndValidateMessage(const std::vector<std::byte> & message) const
 
- {
 
- 	JsonParsingSettings parserSettings;
 
- 	parserSettings.mode = JsonParsingSettings::JsonFormatMode::JSON;
 
- 	parserSettings.maxDepth = 2;
 
- 	parserSettings.strict = true;
 
- 	JsonNode json;
 
- 	try
 
- 	{
 
- 		JsonNode jsonTemp(message.data(), message.size(), "<lobby message>");
 
- 		json = std::move(jsonTemp);
 
- 	}
 
- 	catch (const JsonFormatException & e)
 
- 	{
 
- 		logGlobal->info(std::string("Json parsing error encountered: ") + e.what());
 
- 		return JsonNode();
 
- 	}
 
- 	std::string messageType = json["type"].String();
 
- 	if (messageType.empty())
 
- 	{
 
- 		logGlobal->info("Json parsing error encountered: Message type not set!");
 
- 		return JsonNode();
 
- 	}
 
- 	std::string schemaName = "vcmi:lobbyProtocol/" + messageType;
 
- 	if (!JsonUtils::validate(json, schemaName, messageType + " pack"))
 
- 	{
 
- 		logGlobal->info("Json validation error encountered!");
 
- 		assert(0);
 
- 		return JsonNode();
 
- 	}
 
- 	return json;
 
- }
 
- void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, const std::vector<std::byte> & message)
 
- {
 
- 	// proxy connection - no processing, only redirect
 
- 	if(activeProxies.count(connection))
 
- 	{
 
- 		auto lockedPtr = activeProxies.at(connection);
 
- 		if(lockedPtr)
 
- 			return lockedPtr->sendPacket(message);
 
- 		logGlobal->info("Received unexpected message for inactive proxy!");
 
- 	}
 
- 	JsonNode json = parseAndValidateMessage(message);
 
- 	std::string messageType = json["type"].String();
 
- 	// communication messages from vcmiclient
 
- 	if(activeAccounts.count(connection))
 
- 	{
 
- 		std::string accountName = activeAccounts.at(connection);
 
- 		logGlobal->info("%s: Received message of type %s", accountName, messageType);
 
- 		if(messageType == "sendChatMessage")
 
- 			return receiveSendChatMessage(connection, json);
 
- 		if(messageType == "requestChatHistory")
 
- 			return receiveRequestChatHistory(connection, json);
 
- 		if(messageType == "activateGameRoom")
 
- 			return receiveActivateGameRoom(connection, json);
 
- 		if(messageType == "joinGameRoom")
 
- 			return receiveJoinGameRoom(connection, json);
 
- 		if(messageType == "sendInvite")
 
- 			return receiveSendInvite(connection, json);
 
- 		logGlobal->warn("%s: Unknown message type: %s", accountName, messageType);
 
- 		return;
 
- 	}
 
- 	// communication messages from vcmiserver
 
- 	if(activeGameRooms.count(connection))
 
- 	{
 
- 		std::string roomName = activeGameRooms.at(connection);
 
- 		logGlobal->info("%s: Received message of type %s", roomName, messageType);
 
- 		if(messageType == "changeRoomDescription")
 
- 			return receiveChangeRoomDescription(connection, json);
 
- 		if(messageType == "gameStarted")
 
- 			return receiveGameStarted(connection, json);
 
- 		if(messageType == "leaveGameRoom")
 
- 			return receiveLeaveGameRoom(connection, json);
 
- 		logGlobal->warn("%s: Unknown message type: %s", roomName, messageType);
 
- 		return;
 
- 	}
 
- 	logGlobal->info("(unauthorised): Received message of type %s", messageType);
 
- 	// unauthorized connections - permit only login or register attempts
 
- 	if(messageType == "clientLogin")
 
- 		return receiveClientLogin(connection, json);
 
- 	if(messageType == "clientRegister")
 
- 		return receiveClientRegister(connection, json);
 
- 	if(messageType == "serverLogin")
 
- 		return receiveServerLogin(connection, json);
 
- 	if(messageType == "clientProxyLogin")
 
- 		return receiveClientProxyLogin(connection, json);
 
- 	if(messageType == "serverProxyLogin")
 
- 		return receiveServerProxyLogin(connection, json);
 
- 	connection->close();
 
- 	logGlobal->info("(unauthorised): Unknown message type %s", messageType);
 
- }
 
- void LobbyServer::receiveRequestChatHistory(const NetworkConnectionPtr & connection, const JsonNode & json)
 
- {
 
- 	std::string accountID = activeAccounts[connection];
 
- 	std::string channelType = json["channelType"].String();
 
- 	std::string channelName = json["channelName"].String();
 
- 	if (channelType == "global")
 
- 	{
 
- 		// can only be sent on connection, initiated by server
 
- 		sendOperationFailed(connection, "Operation not supported!");
 
- 	}
 
- 	if (channelType == "match")
 
- 	{
 
- 		if (!database->isPlayerInGameRoom(accountID, channelName))
 
- 			return sendOperationFailed(connection, "Can not access room you are not part of!");
 
- 		sendFullChatHistory(connection, channelType, channelName, channelName);
 
- 	}
 
- 	if (channelType == "player")
 
- 	{
 
- 		if (!database->isAccountIDExists(channelName))
 
- 			return sendOperationFailed(connection, "Such player does not exists!");
 
- 		// room ID for private messages is actually <player 1 ID>_<player 2 ID>, with player ID's sorted alphabetically (to generate unique room ID)
 
- 		std::string roomID = std::min(accountID, channelName) + "_" + std::max(accountID, channelName);
 
- 		sendFullChatHistory(connection, channelType, roomID, channelName);
 
- 	}
 
- }
 
- void LobbyServer::receiveSendChatMessage(const NetworkConnectionPtr & connection, const JsonNode & json)
 
- {
 
- 	std::string senderAccountID = activeAccounts[connection];
 
- 	std::string messageText = json["messageText"].String();
 
- 	std::string channelType = json["channelType"].String();
 
- 	std::string channelName = json["channelName"].String();
 
- 	std::string displayName = database->getAccountDisplayName(senderAccountID);
 
- 	if(!TextOperations::isValidUnicodeString(messageText))
 
- 		return sendOperationFailed(connection, "String contains invalid characters!");
 
- 	std::string messageTextClean = sanitizeChatMessage(messageText);
 
- 	if(messageTextClean.empty())
 
- 		return sendOperationFailed(connection, "No printable characters in sent message!");
 
- 	if (channelType == "global")
 
- 	{
 
- 		try
 
- 		{
 
- 			Languages::getLanguageOptions(channelName);
 
- 		}
 
- 		catch (const std::out_of_range &)
 
- 		{
 
- 			return sendOperationFailed(connection, "Unknown language!");
 
- 		}
 
- 		database->insertChatMessage(senderAccountID, channelType, channelName, messageText);
 
- 		for(const auto & otherConnection : activeAccounts)
 
- 			sendChatMessage(otherConnection.first, channelType, channelName, senderAccountID, displayName, messageText);
 
- 	}
 
- 	if (channelType == "match")
 
- 	{
 
- 		if (!database->isPlayerInGameRoom(senderAccountID, channelName))
 
- 			return sendOperationFailed(connection, "Can not access room you are not part of!");
 
- 		database->insertChatMessage(senderAccountID, channelType, channelName, messageText);
 
- 		LobbyRoomState roomStatus = database->getGameRoomStatus(channelName);
 
- 		// Broadcast chat message only if it being sent to already closed match
 
- 		// Othervice it will be handled by match server
 
- 		if (roomStatus == LobbyRoomState::CLOSED)
 
- 		{
 
- 			for(const auto & otherConnection : activeAccounts)
 
- 			{
 
- 				if (database->isPlayerInGameRoom(otherConnection.second, channelName))
 
- 					sendChatMessage(otherConnection.first, channelType, channelName, senderAccountID, displayName, messageText);
 
- 			}
 
- 		}
 
- 	}
 
- 	if (channelType == "player")
 
- 	{
 
- 		const std::string & receiverAccountID = channelName;
 
- 		std::string roomID = std::min(senderAccountID, receiverAccountID) + "_" + std::max(senderAccountID, receiverAccountID);
 
- 		if (!database->isAccountIDExists(receiverAccountID))
 
- 			return sendOperationFailed(connection, "Such player does not exists!");
 
- 		database->insertChatMessage(senderAccountID, channelType, roomID, messageText);
 
- 		sendChatMessage(connection, channelType, receiverAccountID, senderAccountID, displayName, messageText);
 
- 		if (senderAccountID != receiverAccountID)
 
- 		{
 
- 			for(const auto & otherConnection : activeAccounts)
 
- 				if (otherConnection.second == receiverAccountID)
 
- 					sendChatMessage(otherConnection.first, channelType, senderAccountID, senderAccountID, displayName, messageText);
 
- 		}
 
- 	}
 
- }
 
- void LobbyServer::receiveClientRegister(const NetworkConnectionPtr & connection, const JsonNode & json)
 
- {
 
- 	std::string displayName = json["displayName"].String();
 
- 	std::string language = json["language"].String();
 
- 	if(!isAccountNameValid(displayName))
 
- 		return sendOperationFailed(connection, "Illegal account name");
 
- 	if(database->isAccountNameExists(displayName))
 
- 		return sendOperationFailed(connection, "Account name already in use");
 
- 	std::string accountCookie = boost::uuids::to_string(boost::uuids::random_generator()());
 
- 	std::string accountID = boost::uuids::to_string(boost::uuids::random_generator()());
 
- 	database->insertAccount(accountID, displayName);
 
- 	database->insertAccessCookie(accountID, accountCookie);
 
- 	sendAccountCreated(connection, accountID, accountCookie);
 
- }
 
- void LobbyServer::receiveClientLogin(const NetworkConnectionPtr & connection, const JsonNode & json)
 
- {
 
- 	std::string accountID = json["accountID"].String();
 
- 	std::string accountCookie = json["accountCookie"].String();
 
- 	std::string language = json["language"].String();
 
- 	std::string version = json["version"].String();
 
- 	if(!database->isAccountIDExists(accountID))
 
- 		return sendOperationFailed(connection, "Account not found");
 
- 	auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie);
 
- 	if(clientCookieStatus == LobbyCookieStatus::INVALID)
 
- 		return sendOperationFailed(connection, "Authentication failure");
 
- 	database->updateAccountLoginTime(accountID);
 
- 	database->setAccountOnline(accountID, true);
 
- 	std::string displayName = database->getAccountDisplayName(accountID);
 
- 	activeAccounts[connection] = accountID;
 
- 	logGlobal->info("%s: Logged in as %s", accountID, displayName);
 
- 	sendClientLoginSuccess(connection, accountCookie, displayName);
 
- 	sendRecentChatHistory(connection, "global", "english");
 
- 	if (language != "english")
 
- 		sendRecentChatHistory(connection, "global", language);
 
- 	// send active game rooms list to new account
 
- 	// and update account list to everybody else including new account
 
- 	broadcastActiveAccounts();
 
- 	sendMessage(connection, prepareActiveGameRooms());
 
- 	sendMatchesHistory(connection);
 
- }
 
- void LobbyServer::receiveServerLogin(const NetworkConnectionPtr & connection, const JsonNode & json)
 
- {
 
- 	std::string gameRoomID = json["gameRoomID"].String();
 
- 	std::string accountID = json["accountID"].String();
 
- 	std::string accountCookie = json["accountCookie"].String();
 
- 	std::string version = json["version"].String();
 
- 	auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie);
 
- 	if(clientCookieStatus == LobbyCookieStatus::INVALID)
 
- 	{
 
- 		sendOperationFailed(connection, "Invalid credentials");
 
- 	}
 
- 	else
 
- 	{
 
- 		std::string modListString = json["mods"].isNull() ? "[]" : json["mods"].toCompactString();
 
- 		database->insertGameRoom(gameRoomID, accountID, version, modListString);
 
- 		activeGameRooms[connection] = gameRoomID;
 
- 		sendServerLoginSuccess(connection, accountCookie);
 
- 		broadcastActiveGameRooms();
 
- 	}
 
- }
 
- void LobbyServer::receiveClientProxyLogin(const NetworkConnectionPtr & connection, const JsonNode & json)
 
- {
 
- 	std::string gameRoomID = json["gameRoomID"].String();
 
- 	std::string accountID = json["accountID"].String();
 
- 	std::string accountCookie = json["accountCookie"].String();
 
- 	auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie);
 
- 	if(clientCookieStatus != LobbyCookieStatus::INVALID)
 
- 	{
 
- 		for(auto & proxyEntry : awaitingProxies)
 
- 		{
 
- 			if(proxyEntry.accountID != accountID)
 
- 				continue;
 
- 			if(proxyEntry.roomID != gameRoomID)
 
- 				continue;
 
- 			proxyEntry.accountConnection = connection;
 
- 			auto gameRoomConnection = proxyEntry.roomConnection.lock();
 
- 			if(gameRoomConnection)
 
- 			{
 
- 				activeProxies[gameRoomConnection] = connection;
 
- 				activeProxies[connection] = gameRoomConnection;
 
- 			}
 
- 			return;
 
- 		}
 
- 	}
 
- 	sendOperationFailed(connection, "Invalid credentials");
 
- 	connection->close();
 
- }
 
- void LobbyServer::receiveServerProxyLogin(const NetworkConnectionPtr & connection, const JsonNode & json)
 
- {
 
- 	std::string gameRoomID = json["gameRoomID"].String();
 
- 	std::string guestAccountID = json["guestAccountID"].String();
 
- 	std::string accountCookie = json["accountCookie"].String();
 
- 	// FIXME: find host account ID and validate his cookie
 
- 	//auto clientCookieStatus = database->getAccountCookieStatus(hostAccountID, accountCookie, accountCookieLifetime);
 
- 	//if(clientCookieStatus != LobbyCookieStatus::INVALID)
 
- 	{
 
- 		NetworkConnectionPtr targetAccount = findAccount(guestAccountID);
 
- 		if(targetAccount == nullptr)
 
- 		{
 
- 			sendOperationFailed(connection, "Invalid credentials");
 
- 			return; // unknown / disconnected account
 
- 		}
 
- 		sendJoinRoomSuccess(targetAccount, gameRoomID, true);
 
- 		AwaitingProxyState proxy;
 
- 		proxy.accountID = guestAccountID;
 
- 		proxy.roomID = gameRoomID;
 
- 		proxy.roomConnection = connection;
 
- 		awaitingProxies.push_back(proxy);
 
- 		return;
 
- 	}
 
- 	//connection->close();
 
- }
 
- void LobbyServer::receiveActivateGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json)
 
- {
 
- 	std::string hostAccountID = json["hostAccountID"].String();
 
- 	std::string accountID = activeAccounts[connection];
 
- 	int playerLimit = json["playerLimit"].Integer();
 
- 	if(database->isPlayerInGameRoom(accountID))
 
- 		return sendOperationFailed(connection, "Player already in the room!");
 
- 	std::string gameRoomID = database->getIdleGameRoom(hostAccountID);
 
- 	if(gameRoomID.empty())
 
- 		return sendOperationFailed(connection, "Failed to find idle server to join!");
 
- 	std::string roomType = json["roomType"].String();
 
- 	if(roomType != "public" && roomType != "private")
 
- 		return sendOperationFailed(connection, "Invalid room type!");
 
- 	if(roomType == "public")
 
- 		database->setGameRoomStatus(gameRoomID, LobbyRoomState::PUBLIC);
 
- 	if(roomType == "private")
 
- 		database->setGameRoomStatus(gameRoomID, LobbyRoomState::PRIVATE);
 
- 	database->updateRoomPlayerLimit(gameRoomID, playerLimit);
 
- 	database->insertPlayerIntoGameRoom(accountID, gameRoomID);
 
- 	broadcastActiveGameRooms();
 
- 	sendJoinRoomSuccess(connection, gameRoomID, false);
 
- }
 
- void LobbyServer::receiveJoinGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json)
 
- {
 
- 	std::string gameRoomID = json["gameRoomID"].String();
 
- 	std::string accountID = activeAccounts[connection];
 
- 	if(database->isPlayerInGameRoom(accountID))
 
- 		return sendOperationFailed(connection, "Player already in the room!");
 
- 	NetworkConnectionPtr targetRoom = findGameRoom(gameRoomID);
 
- 	if(targetRoom == nullptr)
 
- 		return sendOperationFailed(connection, "Failed to find game room to join!");
 
- 	auto roomStatus = database->getGameRoomStatus(gameRoomID);
 
- 	if(roomStatus != LobbyRoomState::PRIVATE && roomStatus != LobbyRoomState::PUBLIC)
 
- 		return sendOperationFailed(connection, "Room does not accepts new players!");
 
- 	if(roomStatus == LobbyRoomState::PRIVATE)
 
- 	{
 
- 		if(database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::INVITED)
 
- 			return sendOperationFailed(connection, "You are not permitted to join private room without invite!");
 
- 	}
 
- 	if(database->getGameRoomFreeSlots(gameRoomID) == 0)
 
- 		return sendOperationFailed(connection, "Room is already full!");
 
- 	database->insertPlayerIntoGameRoom(accountID, gameRoomID);
 
- 	sendAccountJoinsRoom(targetRoom, accountID);
 
- 	//No reply to client - will be sent once match server establishes proxy connection with lobby
 
- 	broadcastActiveGameRooms();
 
- }
 
- void LobbyServer::receiveChangeRoomDescription(const NetworkConnectionPtr & connection, const JsonNode & json)
 
- {
 
- 	std::string gameRoomID = activeGameRooms[connection];
 
- 	std::string description = json["description"].String();
 
- 	database->updateRoomDescription(gameRoomID, description);
 
- 	broadcastActiveGameRooms();
 
- }
 
- void LobbyServer::receiveGameStarted(const NetworkConnectionPtr & connection, const JsonNode & json)
 
- {
 
- 	std::string gameRoomID = activeGameRooms[connection];
 
- 	database->setGameRoomStatus(gameRoomID, LobbyRoomState::BUSY);
 
- 	broadcastActiveGameRooms();
 
- }
 
- void LobbyServer::receiveLeaveGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json)
 
- {
 
- 	std::string accountID = json["accountID"].String();
 
- 	std::string gameRoomID = activeGameRooms[connection];
 
- 	if(!database->isPlayerInGameRoom(accountID, gameRoomID))
 
- 		return sendOperationFailed(connection, "You are not in the room!");
 
- 	database->deletePlayerFromGameRoom(accountID, gameRoomID);
 
- 	broadcastActiveGameRooms();
 
- }
 
- void LobbyServer::receiveSendInvite(const NetworkConnectionPtr & connection, const JsonNode & json)
 
- {
 
- 	std::string senderName = activeAccounts[connection];
 
- 	std::string accountID = json["accountID"].String();
 
- 	std::string gameRoomID = database->getAccountGameRoom(senderName);
 
- 	auto targetAccountConnection = findAccount(accountID);
 
- 	if(!targetAccountConnection)
 
- 		return sendOperationFailed(connection, "Player is offline or does not exists!");
 
- 	if(!database->isPlayerInGameRoom(senderName))
 
- 		return sendOperationFailed(connection, "You are not in the room!");
 
- 	if(database->isPlayerInGameRoom(accountID))
 
- 		return sendOperationFailed(connection, "This player is already in a room!");
 
- 	if(database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::NOT_INVITED)
 
- 		return sendOperationFailed(connection, "This player is already invited!");
 
- 	database->insertGameRoomInvite(accountID, gameRoomID);
 
- 	sendInviteReceived(targetAccountConnection, senderName, gameRoomID);
 
- 	broadcastActiveGameRooms();
 
- }
 
- LobbyServer::~LobbyServer() = default;
 
- LobbyServer::LobbyServer(const boost::filesystem::path & databasePath)
 
- 	: database(std::make_unique<LobbyDatabase>(databasePath))
 
- 	, networkHandler(INetworkHandler::createHandler())
 
- 	, networkServer(networkHandler->createServerTCP(*this))
 
- {
 
- }
 
- void LobbyServer::start(uint16_t port)
 
- {
 
- 	networkServer->start(port);
 
- }
 
- void LobbyServer::run()
 
- {
 
- 	networkHandler->run();
 
- }
 
 
  |