123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653 |
- /*
- * 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 <boost/uuid/uuid_generators.hpp>
- #include <boost/uuid/uuid_io.hpp>
- bool LobbyServer::isAccountNameValid(const std::string & accountName) const
- {
- if(accountName.size() < 4)
- return false;
- if(accountName.size() > 20)
- return false;
- for(const auto & c : accountName)
- if(!std::isalnum(c))
- return false;
- return true;
- }
- std::string LobbyServer::sanitizeChatMessage(const std::string & inputString) const
- {
- // TODO: sanitize message and remove any "weird" symbols from it
- return inputString;
- }
- 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)
- {
- 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::sendChatHistory(const NetworkConnectionPtr & target, const std::vector<LobbyChatMessage> & history)
- {
- JsonNode reply;
- reply["type"].String() = "chatHistory";
- 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);
- }
- 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)
- {
- JsonNode jsonEntry;
- jsonEntry["gameRoomID"].String() = gameRoom.roomID;
- jsonEntry["hostAccountID"].String() = gameRoom.hostAccountID;
- jsonEntry["hostAccountDisplayName"].String() = gameRoom.hostAccountDisplayName;
- jsonEntry["description"].String() = gameRoom.description;
- jsonEntry["playersCount"].Integer() = gameRoom.playersCount;
- jsonEntry["playersLimit"].Integer() = gameRoom.playersLimit;
- reply["gameRooms"].Vector().push_back(jsonEntry);
- }
- 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 & roomMode, const std::string & roomName, 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["roomMode"].String() = roomMode;
- reply["roomName"].String() = roomName;
- sendMessage(target, reply);
- }
- void LobbyServer::onNewConnection(const NetworkConnectionPtr & connection)
- {
- // no-op - waiting for incoming data
- }
- void LobbyServer::onDisconnected(const NetworkConnectionPtr & connection, const std::string & errorMessage)
- {
- if(activeAccounts.count(connection))
- {
- database->setAccountOnline(activeAccounts.at(connection), false);
- activeAccounts.erase(connection);
- }
- if(activeGameRooms.count(connection))
- {
- database->setGameRoomStatus(activeGameRooms.at(connection), LobbyRoomState::CLOSED);
- 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());
- 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 == "activateGameRoom")
- return receiveActivateGameRoom(connection, json);
- if(messageType == "joinGameRoom")
- return receiveJoinGameRoom(connection, json);
- if(messageType == "sendInvite")
- return receiveSendInvite(connection, json);
- if(messageType == "declineInvite")
- return receiveDeclineInvite(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 == "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::receiveSendChatMessage(const NetworkConnectionPtr & connection, const JsonNode & json)
- {
- std::string accountID = activeAccounts[connection];
- std::string messageText = json["messageText"].String();
- std::string messageTextClean = sanitizeChatMessage(messageText);
- std::string displayName = database->getAccountDisplayName(accountID);
- if(messageTextClean.empty())
- return sendOperationFailed(connection, "No printable characters in sent message!");
- database->insertChatMessage(accountID, "global", "english", messageText);
- for(const auto & otherConnection : activeAccounts)
- sendChatMessage(otherConnection.first, "global", "english", accountID, 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, "Authentification failure");
- database->updateAccountLoginTime(accountID);
- database->setAccountOnline(accountID, true);
- std::string displayName = database->getAccountDisplayName(accountID);
- activeAccounts[connection] = accountID;
- sendClientLoginSuccess(connection, accountCookie, displayName);
- sendChatHistory(connection, database->getRecentMessageHistory());
- // send active game rooms list to new account
- // and update acount list to everybody else including new account
- broadcastActiveAccounts();
- sendMessage(connection, prepareActiveGameRooms());
- }
- 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
- {
- database->insertGameRoom(gameRoomID, accountID);
- 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];
- 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->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::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 targetAccount = findAccount(accountID);
- if(!targetAccount)
- return sendOperationFailed(connection, "Invalid account to invite!");
- 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(targetAccount, senderName, gameRoomID);
- }
- void LobbyServer::receiveDeclineInvite(const NetworkConnectionPtr & connection, const JsonNode & json)
- {
- std::string accountID = activeAccounts[connection];
- std::string gameRoomID = json["gameRoomID"].String();
- if(database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::INVITED)
- return sendOperationFailed(connection, "No active invite found!");
- database->deleteGameRoomInvite(accountID, gameRoomID);
- }
- 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();
- }
|