LobbyServer.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852
  1. /*
  2. * LobbyServer.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "LobbyServer.h"
  12. #include "LobbyDatabase.h"
  13. #include "../lib/json/JsonFormatException.h"
  14. #include "../lib/json/JsonNode.h"
  15. #include "../lib/json/JsonUtils.h"
  16. #include "../lib/texts/Languages.h"
  17. #include "../lib/texts/TextOperations.h"
  18. #include <boost/uuid/uuid_generators.hpp>
  19. #include <boost/uuid/uuid_io.hpp>
  20. bool LobbyServer::isAccountNameValid(const std::string & accountName) const
  21. {
  22. // Arbitrary limit on account name length.
  23. // Can be extended if there are no issues with UI space
  24. if(accountName.size() < 4)
  25. return false;
  26. if(accountName.size() > 20)
  27. return false;
  28. // For now permit only latin alphabet and numbers
  29. // Can be extended, but makes sure that such symbols will be present in all H3 fonts
  30. for(const auto & c : accountName)
  31. if(!std::isalnum(c))
  32. return false;
  33. return true;
  34. }
  35. std::string LobbyServer::sanitizeChatMessage(const std::string & inputString) const
  36. {
  37. static const std::string blacklist = "{}";
  38. std::string sanitized;
  39. for(const auto & ch : inputString)
  40. {
  41. // Remove all control characters
  42. if (ch >= '\0' && ch < ' ')
  43. continue;
  44. // Remove blacklisted characters such as brackets that are used for text formatting
  45. if (blacklist.find(ch) != std::string::npos)
  46. continue;
  47. sanitized += ch;
  48. }
  49. return boost::trim_copy(sanitized);
  50. }
  51. NetworkConnectionPtr LobbyServer::findAccount(const std::string & accountID) const
  52. {
  53. for(const auto & account : activeAccounts)
  54. if(account.second == accountID)
  55. return account.first;
  56. return nullptr;
  57. }
  58. NetworkConnectionPtr LobbyServer::findGameRoom(const std::string & gameRoomID) const
  59. {
  60. for(const auto & account : activeGameRooms)
  61. if(account.second == gameRoomID)
  62. return account.first;
  63. return nullptr;
  64. }
  65. void LobbyServer::sendMessage(const NetworkConnectionPtr & target, const JsonNode & json)
  66. {
  67. logGlobal->info("Sending message of type %s", json["type"].String());
  68. assert(JsonUtils::validate(json, "vcmi:lobbyProtocol/" + json["type"].String(), json["type"].String() + " pack"));
  69. target->sendPacket(json.toBytes());
  70. }
  71. void LobbyServer::sendAccountCreated(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & accountCookie)
  72. {
  73. JsonNode reply;
  74. reply["type"].String() = "accountCreated";
  75. reply["accountID"].String() = accountID;
  76. reply["accountCookie"].String() = accountCookie;
  77. sendMessage(target, reply);
  78. }
  79. void LobbyServer::sendInviteReceived(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & gameRoomID)
  80. {
  81. JsonNode reply;
  82. reply["type"].String() = "inviteReceived";
  83. reply["accountID"].String() = accountID;
  84. reply["gameRoomID"].String() = gameRoomID;
  85. sendMessage(target, reply);
  86. }
  87. void LobbyServer::sendOperationFailed(const NetworkConnectionPtr & target, const std::string & reason)
  88. {
  89. JsonNode reply;
  90. reply["type"].String() = "operationFailed";
  91. reply["reason"].String() = reason;
  92. sendMessage(target, reply);
  93. }
  94. void LobbyServer::sendClientLoginSuccess(const NetworkConnectionPtr & target, const std::string & accountCookie, const std::string & displayName)
  95. {
  96. JsonNode reply;
  97. reply["type"].String() = "clientLoginSuccess";
  98. reply["accountCookie"].String() = accountCookie;
  99. reply["displayName"].String() = displayName;
  100. sendMessage(target, reply);
  101. }
  102. void LobbyServer::sendServerLoginSuccess(const NetworkConnectionPtr & target, const std::string & accountCookie)
  103. {
  104. JsonNode reply;
  105. reply["type"].String() = "serverLoginSuccess";
  106. reply["accountCookie"].String() = accountCookie;
  107. sendMessage(target, reply);
  108. }
  109. void LobbyServer::sendFullChatHistory(const NetworkConnectionPtr & target, const std::string & channelType, const std::string & channelName, const std::string & channelNameForClient)
  110. {
  111. sendChatHistory(target, channelType, channelNameForClient, database->getFullMessageHistory(channelType, channelName));
  112. }
  113. void LobbyServer::sendRecentChatHistory(const NetworkConnectionPtr & target, const std::string & channelType, const std::string & channelName)
  114. {
  115. sendChatHistory(target, channelType, channelName, database->getRecentMessageHistory(channelType, channelName));
  116. }
  117. void LobbyServer::sendChatHistory(const NetworkConnectionPtr & target, const std::string & channelType, const std::string & channelName, const std::vector<LobbyChatMessage> & history)
  118. {
  119. JsonNode reply;
  120. reply["type"].String() = "chatHistory";
  121. reply["channelType"].String() = channelType;
  122. reply["channelName"].String() = channelName;
  123. reply["messages"].Vector(); // force creation of empty vector
  124. for(const auto & message : boost::adaptors::reverse(history))
  125. {
  126. JsonNode jsonEntry;
  127. jsonEntry["accountID"].String() = message.accountID;
  128. jsonEntry["displayName"].String() = message.displayName;
  129. jsonEntry["messageText"].String() = message.messageText;
  130. jsonEntry["ageSeconds"].Integer() = message.age.count();
  131. reply["messages"].Vector().push_back(jsonEntry);
  132. }
  133. sendMessage(target, reply);
  134. }
  135. void LobbyServer::broadcastActiveAccounts()
  136. {
  137. auto activeAccountsStats = database->getActiveAccounts();
  138. JsonNode reply;
  139. reply["type"].String() = "activeAccounts";
  140. reply["accounts"].Vector(); // force creation of empty vector
  141. for(const auto & account : activeAccountsStats)
  142. {
  143. JsonNode jsonEntry;
  144. jsonEntry["accountID"].String() = account.accountID;
  145. jsonEntry["displayName"].String() = account.displayName;
  146. jsonEntry["status"].String() = "In Lobby"; // TODO: in room status, in match status, offline status(?)
  147. reply["accounts"].Vector().push_back(jsonEntry);
  148. }
  149. for(const auto & connection : activeAccounts)
  150. sendMessage(connection.first, reply);
  151. }
  152. static JsonNode loadLobbyAccountToJson(const LobbyAccount & account)
  153. {
  154. JsonNode jsonEntry;
  155. jsonEntry["accountID"].String() = account.accountID;
  156. jsonEntry["displayName"].String() = account.displayName;
  157. return jsonEntry;
  158. }
  159. static JsonNode loadLobbyGameRoomToJson(const LobbyGameRoom & gameRoom)
  160. {
  161. static constexpr std::array LOBBY_ROOM_STATE_NAMES = {
  162. "idle",
  163. "public",
  164. "private",
  165. "busy",
  166. "cancelled",
  167. "closed"
  168. };
  169. JsonNode jsonEntry;
  170. jsonEntry["gameRoomID"].String() = gameRoom.roomID;
  171. jsonEntry["hostAccountID"].String() = gameRoom.hostAccountID;
  172. jsonEntry["hostAccountDisplayName"].String() = gameRoom.hostAccountDisplayName;
  173. jsonEntry["description"].String() = gameRoom.description;
  174. jsonEntry["version"].String() = gameRoom.version;
  175. jsonEntry["status"].String() = LOBBY_ROOM_STATE_NAMES[vstd::to_underlying(gameRoom.roomState)];
  176. jsonEntry["playerLimit"].Integer() = gameRoom.playerLimit;
  177. jsonEntry["ageSeconds"].Integer() = gameRoom.age.count();
  178. if (!gameRoom.modsJson.empty()) // not present in match history
  179. jsonEntry["mods"] = JsonNode(reinterpret_cast<const std::byte *>(gameRoom.modsJson.data()), gameRoom.modsJson.size(), "<lobby "+gameRoom.roomID+">");
  180. for(const auto & account : gameRoom.participants)
  181. jsonEntry["participants"].Vector().push_back(loadLobbyAccountToJson(account));
  182. for(const auto & account : gameRoom.invited)
  183. jsonEntry["invited"].Vector().push_back(loadLobbyAccountToJson(account));
  184. return jsonEntry;
  185. }
  186. void LobbyServer::sendMatchesHistory(const NetworkConnectionPtr & target)
  187. {
  188. std::string accountID = activeAccounts.at(target);
  189. auto matchesHistory = database->getAccountGameHistory(accountID);
  190. JsonNode reply;
  191. reply["type"].String() = "matchesHistory";
  192. reply["matchesHistory"].Vector(); // force creation of empty vector
  193. for(const auto & gameRoom : matchesHistory)
  194. reply["matchesHistory"].Vector().push_back(loadLobbyGameRoomToJson(gameRoom));
  195. sendMessage(target, reply);
  196. }
  197. JsonNode LobbyServer::prepareActiveGameRooms()
  198. {
  199. auto activeGameRoomStats = database->getActiveGameRooms();
  200. JsonNode reply;
  201. reply["type"].String() = "activeGameRooms";
  202. reply["gameRooms"].Vector(); // force creation of empty vector
  203. for(const auto & gameRoom : activeGameRoomStats)
  204. reply["gameRooms"].Vector().push_back(loadLobbyGameRoomToJson(gameRoom));
  205. return reply;
  206. }
  207. void LobbyServer::broadcastActiveGameRooms()
  208. {
  209. auto reply = prepareActiveGameRooms();
  210. for(const auto & connection : activeAccounts)
  211. sendMessage(connection.first, reply);
  212. }
  213. void LobbyServer::sendAccountJoinsRoom(const NetworkConnectionPtr & target, const std::string & accountID)
  214. {
  215. JsonNode reply;
  216. reply["type"].String() = "accountJoinsRoom";
  217. reply["accountID"].String() = accountID;
  218. sendMessage(target, reply);
  219. }
  220. void LobbyServer::sendJoinRoomSuccess(const NetworkConnectionPtr & target, const std::string & gameRoomID, bool proxyMode)
  221. {
  222. JsonNode reply;
  223. reply["type"].String() = "joinRoomSuccess";
  224. reply["gameRoomID"].String() = gameRoomID;
  225. reply["proxyMode"].Bool() = proxyMode;
  226. sendMessage(target, reply);
  227. }
  228. 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)
  229. {
  230. JsonNode reply;
  231. reply["type"].String() = "chatMessage";
  232. reply["messageText"].String() = messageText;
  233. reply["accountID"].String() = accountID;
  234. reply["displayName"].String() = displayName;
  235. reply["channelType"].String() = channelType;
  236. reply["channelName"].String() = channelName;
  237. sendMessage(target, reply);
  238. }
  239. void LobbyServer::onNewConnection(const NetworkConnectionPtr & connection)
  240. {
  241. connection->setAsyncWritesEnabled(true);
  242. // no-op - waiting for incoming data
  243. }
  244. void LobbyServer::onDisconnected(const NetworkConnectionPtr & connection, const std::string & errorMessage)
  245. {
  246. if(activeAccounts.count(connection))
  247. {
  248. logGlobal->info("Account %s disconnecting. Accounts online: %d", activeAccounts.at(connection), activeAccounts.size() - 1);
  249. database->setAccountOnline(activeAccounts.at(connection), false);
  250. activeAccounts.erase(connection);
  251. }
  252. if(activeGameRooms.count(connection))
  253. {
  254. std::string gameRoomID = activeGameRooms.at(connection);
  255. logGlobal->info("Game room %s disconnecting. Rooms online: %d", gameRoomID, activeGameRooms.size() - 1);
  256. if (database->getGameRoomStatus(gameRoomID) == LobbyRoomState::BUSY)
  257. {
  258. database->setGameRoomStatus(gameRoomID, LobbyRoomState::CLOSED);
  259. for(const auto & accountConnection : activeAccounts)
  260. if (database->isPlayerInGameRoom(accountConnection.second, gameRoomID))
  261. sendMatchesHistory(accountConnection.first);
  262. }
  263. else
  264. database->setGameRoomStatus(gameRoomID, LobbyRoomState::CANCELLED);
  265. activeGameRooms.erase(connection);
  266. }
  267. if(activeProxies.count(connection))
  268. {
  269. const auto otherConnection = activeProxies.at(connection);
  270. if (otherConnection)
  271. otherConnection->close();
  272. activeProxies.erase(connection);
  273. activeProxies.erase(otherConnection);
  274. }
  275. broadcastActiveAccounts();
  276. broadcastActiveGameRooms();
  277. }
  278. JsonNode LobbyServer::parseAndValidateMessage(const std::vector<std::byte> & message) const
  279. {
  280. JsonParsingSettings parserSettings;
  281. parserSettings.mode = JsonParsingSettings::JsonFormatMode::JSON;
  282. parserSettings.maxDepth = 2;
  283. parserSettings.strict = true;
  284. JsonNode json;
  285. try
  286. {
  287. JsonNode jsonTemp(message.data(), message.size(), "<lobby message>");
  288. json = std::move(jsonTemp);
  289. }
  290. catch (const JsonFormatException & e)
  291. {
  292. logGlobal->info(std::string("Json parsing error encountered: ") + e.what());
  293. return JsonNode();
  294. }
  295. std::string messageType = json["type"].String();
  296. if (messageType.empty())
  297. {
  298. logGlobal->info("Json parsing error encountered: Message type not set!");
  299. return JsonNode();
  300. }
  301. std::string schemaName = "vcmi:lobbyProtocol/" + messageType;
  302. if (!JsonUtils::validate(json, schemaName, messageType + " pack"))
  303. {
  304. logGlobal->info("Json validation error encountered!");
  305. assert(0);
  306. return JsonNode();
  307. }
  308. return json;
  309. }
  310. void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, const std::vector<std::byte> & message)
  311. {
  312. // proxy connection - no processing, only redirect
  313. if(activeProxies.count(connection))
  314. {
  315. auto lockedPtr = activeProxies.at(connection);
  316. if(lockedPtr)
  317. return lockedPtr->sendPacket(message);
  318. logGlobal->info("Received unexpected message for inactive proxy!");
  319. }
  320. JsonNode json = parseAndValidateMessage(message);
  321. std::string messageType = json["type"].String();
  322. // communication messages from vcmiclient
  323. if(activeAccounts.count(connection))
  324. {
  325. std::string accountName = activeAccounts.at(connection);
  326. logGlobal->info("%s: Received message of type %s", accountName, messageType);
  327. if(messageType == "sendChatMessage")
  328. return receiveSendChatMessage(connection, json);
  329. if(messageType == "requestChatHistory")
  330. return receiveRequestChatHistory(connection, json);
  331. if(messageType == "activateGameRoom")
  332. return receiveActivateGameRoom(connection, json);
  333. if(messageType == "joinGameRoom")
  334. return receiveJoinGameRoom(connection, json);
  335. if(messageType == "sendInvite")
  336. return receiveSendInvite(connection, json);
  337. logGlobal->warn("%s: Unknown message type: %s", accountName, messageType);
  338. return;
  339. }
  340. // communication messages from vcmiserver
  341. if(activeGameRooms.count(connection))
  342. {
  343. std::string roomName = activeGameRooms.at(connection);
  344. logGlobal->info("%s: Received message of type %s", roomName, messageType);
  345. if(messageType == "changeRoomDescription")
  346. return receiveChangeRoomDescription(connection, json);
  347. if(messageType == "gameStarted")
  348. return receiveGameStarted(connection, json);
  349. if(messageType == "leaveGameRoom")
  350. return receiveLeaveGameRoom(connection, json);
  351. logGlobal->warn("%s: Unknown message type: %s", roomName, messageType);
  352. return;
  353. }
  354. logGlobal->info("(unauthorised): Received message of type %s", messageType);
  355. // unauthorized connections - permit only login or register attempts
  356. if(messageType == "clientLogin")
  357. return receiveClientLogin(connection, json);
  358. if(messageType == "clientRegister")
  359. return receiveClientRegister(connection, json);
  360. if(messageType == "serverLogin")
  361. return receiveServerLogin(connection, json);
  362. if(messageType == "clientProxyLogin")
  363. return receiveClientProxyLogin(connection, json);
  364. if(messageType == "serverProxyLogin")
  365. return receiveServerProxyLogin(connection, json);
  366. connection->close();
  367. logGlobal->info("(unauthorised): Unknown message type %s", messageType);
  368. }
  369. void LobbyServer::receiveRequestChatHistory(const NetworkConnectionPtr & connection, const JsonNode & json)
  370. {
  371. std::string accountID = activeAccounts[connection];
  372. std::string channelType = json["channelType"].String();
  373. std::string channelName = json["channelName"].String();
  374. if (channelType == "global")
  375. {
  376. sendRecentChatHistory(connection, channelType, channelName);
  377. }
  378. if (channelType == "match")
  379. {
  380. if (!database->isPlayerInGameRoom(accountID, channelName))
  381. return sendOperationFailed(connection, "Can not access room you are not part of!");
  382. sendFullChatHistory(connection, channelType, channelName, channelName);
  383. }
  384. if (channelType == "player")
  385. {
  386. if (!database->isAccountIDExists(channelName))
  387. return sendOperationFailed(connection, "Such player does not exists!");
  388. // room ID for private messages is actually <player 1 ID>_<player 2 ID>, with player ID's sorted alphabetically (to generate unique room ID)
  389. std::string roomID = std::min(accountID, channelName) + "_" + std::max(accountID, channelName);
  390. sendFullChatHistory(connection, channelType, roomID, channelName);
  391. }
  392. }
  393. void LobbyServer::receiveSendChatMessage(const NetworkConnectionPtr & connection, const JsonNode & json)
  394. {
  395. std::string senderAccountID = activeAccounts[connection];
  396. std::string messageText = json["messageText"].String();
  397. std::string channelType = json["channelType"].String();
  398. std::string channelName = json["channelName"].String();
  399. std::string displayName = database->getAccountDisplayName(senderAccountID);
  400. if(!TextOperations::isValidUnicodeString(messageText))
  401. return sendOperationFailed(connection, "String contains invalid characters!");
  402. std::string messageTextClean = sanitizeChatMessage(messageText);
  403. if(messageTextClean.empty())
  404. return sendOperationFailed(connection, "No printable characters in sent message!");
  405. if (channelType == "global")
  406. {
  407. try
  408. {
  409. Languages::getLanguageOptions(channelName);
  410. }
  411. catch (const std::out_of_range &)
  412. {
  413. return sendOperationFailed(connection, "Unknown language!");
  414. }
  415. database->insertChatMessage(senderAccountID, channelType, channelName, messageText);
  416. for(const auto & otherConnection : activeAccounts)
  417. sendChatMessage(otherConnection.first, channelType, channelName, senderAccountID, displayName, messageText);
  418. }
  419. if (channelType == "match")
  420. {
  421. if (!database->isPlayerInGameRoom(senderAccountID, channelName))
  422. return sendOperationFailed(connection, "Can not access room you are not part of!");
  423. database->insertChatMessage(senderAccountID, channelType, channelName, messageText);
  424. LobbyRoomState roomStatus = database->getGameRoomStatus(channelName);
  425. // Broadcast chat message only if it being sent to already closed match
  426. // Othervice it will be handled by match server
  427. if (roomStatus == LobbyRoomState::CLOSED)
  428. {
  429. for(const auto & otherConnection : activeAccounts)
  430. {
  431. if (database->isPlayerInGameRoom(otherConnection.second, channelName))
  432. sendChatMessage(otherConnection.first, channelType, channelName, senderAccountID, displayName, messageText);
  433. }
  434. }
  435. }
  436. if (channelType == "player")
  437. {
  438. const std::string & receiverAccountID = channelName;
  439. std::string roomID = std::min(senderAccountID, receiverAccountID) + "_" + std::max(senderAccountID, receiverAccountID);
  440. if (!database->isAccountIDExists(receiverAccountID))
  441. return sendOperationFailed(connection, "Such player does not exists!");
  442. database->insertChatMessage(senderAccountID, channelType, roomID, messageText);
  443. sendChatMessage(connection, channelType, receiverAccountID, senderAccountID, displayName, messageText);
  444. if (senderAccountID != receiverAccountID)
  445. {
  446. for(const auto & otherConnection : activeAccounts)
  447. if (otherConnection.second == receiverAccountID)
  448. sendChatMessage(otherConnection.first, channelType, senderAccountID, senderAccountID, displayName, messageText);
  449. }
  450. }
  451. }
  452. void LobbyServer::receiveClientRegister(const NetworkConnectionPtr & connection, const JsonNode & json)
  453. {
  454. std::string displayName = json["displayName"].String();
  455. std::string language = json["language"].String();
  456. if(!isAccountNameValid(displayName))
  457. return sendOperationFailed(connection, "Illegal account name");
  458. if(database->isAccountNameExists(displayName))
  459. return sendOperationFailed(connection, "Account name already in use");
  460. std::string accountCookie = boost::uuids::to_string(boost::uuids::random_generator()());
  461. std::string accountID = boost::uuids::to_string(boost::uuids::random_generator()());
  462. database->insertAccount(accountID, displayName);
  463. database->insertAccessCookie(accountID, accountCookie);
  464. sendAccountCreated(connection, accountID, accountCookie);
  465. }
  466. void LobbyServer::receiveClientLogin(const NetworkConnectionPtr & connection, const JsonNode & json)
  467. {
  468. std::string accountID = json["accountID"].String();
  469. std::string accountCookie = json["accountCookie"].String();
  470. std::string language = json["language"].String();
  471. std::string version = json["version"].String();
  472. const auto & languageRooms = json["languageRooms"].Vector();
  473. if(!database->isAccountIDExists(accountID))
  474. return sendOperationFailed(connection, "Account not found");
  475. auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie);
  476. if(clientCookieStatus == LobbyCookieStatus::INVALID)
  477. return sendOperationFailed(connection, "Authentication failure");
  478. database->updateAccountLoginTime(accountID);
  479. database->setAccountOnline(accountID, true);
  480. std::string displayName = database->getAccountDisplayName(accountID);
  481. activeAccounts[connection] = accountID;
  482. logGlobal->info("%s: Logged in as %s", accountID, displayName);
  483. sendClientLoginSuccess(connection, accountCookie, displayName);
  484. if (!languageRooms.empty())
  485. {
  486. for (const auto & entry : languageRooms)
  487. sendRecentChatHistory(connection, "global", entry.String());
  488. }
  489. else
  490. {
  491. sendRecentChatHistory(connection, "global", "english");
  492. if (language != "english")
  493. sendRecentChatHistory(connection, "global", language);
  494. }
  495. // send active game rooms list to new account
  496. // and update account list to everybody else including new account
  497. broadcastActiveAccounts();
  498. sendMessage(connection, prepareActiveGameRooms());
  499. sendMatchesHistory(connection);
  500. }
  501. void LobbyServer::receiveServerLogin(const NetworkConnectionPtr & connection, const JsonNode & json)
  502. {
  503. std::string gameRoomID = json["gameRoomID"].String();
  504. std::string accountID = json["accountID"].String();
  505. std::string accountCookie = json["accountCookie"].String();
  506. std::string version = json["version"].String();
  507. auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie);
  508. if(clientCookieStatus == LobbyCookieStatus::INVALID)
  509. {
  510. sendOperationFailed(connection, "Invalid credentials");
  511. }
  512. else
  513. {
  514. std::string modListString = json["mods"].isNull() ? "[]" : json["mods"].toCompactString();
  515. database->insertGameRoom(gameRoomID, accountID, version, modListString);
  516. activeGameRooms[connection] = gameRoomID;
  517. sendServerLoginSuccess(connection, accountCookie);
  518. broadcastActiveGameRooms();
  519. }
  520. }
  521. void LobbyServer::receiveClientProxyLogin(const NetworkConnectionPtr & connection, const JsonNode & json)
  522. {
  523. std::string gameRoomID = json["gameRoomID"].String();
  524. std::string accountID = json["accountID"].String();
  525. std::string accountCookie = json["accountCookie"].String();
  526. auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie);
  527. if(clientCookieStatus != LobbyCookieStatus::INVALID)
  528. {
  529. for(auto & proxyEntry : awaitingProxies)
  530. {
  531. if(proxyEntry.accountID != accountID)
  532. continue;
  533. if(proxyEntry.roomID != gameRoomID)
  534. continue;
  535. proxyEntry.accountConnection = connection;
  536. auto gameRoomConnection = proxyEntry.roomConnection.lock();
  537. if(gameRoomConnection)
  538. {
  539. activeProxies[gameRoomConnection] = connection;
  540. activeProxies[connection] = gameRoomConnection;
  541. }
  542. return;
  543. }
  544. }
  545. sendOperationFailed(connection, "Invalid credentials");
  546. connection->close();
  547. }
  548. void LobbyServer::receiveServerProxyLogin(const NetworkConnectionPtr & connection, const JsonNode & json)
  549. {
  550. std::string gameRoomID = json["gameRoomID"].String();
  551. std::string guestAccountID = json["guestAccountID"].String();
  552. std::string accountCookie = json["accountCookie"].String();
  553. // FIXME: find host account ID and validate his cookie
  554. //auto clientCookieStatus = database->getAccountCookieStatus(hostAccountID, accountCookie, accountCookieLifetime);
  555. //if(clientCookieStatus != LobbyCookieStatus::INVALID)
  556. {
  557. NetworkConnectionPtr targetAccount = findAccount(guestAccountID);
  558. if(targetAccount == nullptr)
  559. {
  560. sendOperationFailed(connection, "Invalid credentials");
  561. return; // unknown / disconnected account
  562. }
  563. sendJoinRoomSuccess(targetAccount, gameRoomID, true);
  564. AwaitingProxyState proxy;
  565. proxy.accountID = guestAccountID;
  566. proxy.roomID = gameRoomID;
  567. proxy.roomConnection = connection;
  568. awaitingProxies.push_back(proxy);
  569. return;
  570. }
  571. //connection->close();
  572. }
  573. void LobbyServer::receiveActivateGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json)
  574. {
  575. std::string hostAccountID = json["hostAccountID"].String();
  576. std::string accountID = activeAccounts[connection];
  577. int playerLimit = json["playerLimit"].Integer();
  578. if(database->isPlayerInGameRoom(accountID))
  579. return sendOperationFailed(connection, "Player already in the room!");
  580. std::string gameRoomID = database->getIdleGameRoom(hostAccountID);
  581. if(gameRoomID.empty())
  582. return sendOperationFailed(connection, "Failed to find idle server to join!");
  583. std::string roomType = json["roomType"].String();
  584. if(roomType != "public" && roomType != "private")
  585. return sendOperationFailed(connection, "Invalid room type!");
  586. if(roomType == "public")
  587. database->setGameRoomStatus(gameRoomID, LobbyRoomState::PUBLIC);
  588. if(roomType == "private")
  589. database->setGameRoomStatus(gameRoomID, LobbyRoomState::PRIVATE);
  590. database->updateRoomPlayerLimit(gameRoomID, playerLimit);
  591. database->insertPlayerIntoGameRoom(accountID, gameRoomID);
  592. broadcastActiveGameRooms();
  593. sendJoinRoomSuccess(connection, gameRoomID, false);
  594. }
  595. void LobbyServer::receiveJoinGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json)
  596. {
  597. std::string gameRoomID = json["gameRoomID"].String();
  598. std::string accountID = activeAccounts[connection];
  599. if(database->isPlayerInGameRoom(accountID))
  600. return sendOperationFailed(connection, "Player already in the room!");
  601. NetworkConnectionPtr targetRoom = findGameRoom(gameRoomID);
  602. if(targetRoom == nullptr)
  603. return sendOperationFailed(connection, "Failed to find game room to join!");
  604. auto roomStatus = database->getGameRoomStatus(gameRoomID);
  605. if(roomStatus != LobbyRoomState::PRIVATE && roomStatus != LobbyRoomState::PUBLIC)
  606. return sendOperationFailed(connection, "Room does not accepts new players!");
  607. if(roomStatus == LobbyRoomState::PRIVATE)
  608. {
  609. if(database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::INVITED)
  610. return sendOperationFailed(connection, "You are not permitted to join private room without invite!");
  611. }
  612. if(database->getGameRoomFreeSlots(gameRoomID) == 0)
  613. return sendOperationFailed(connection, "Room is already full!");
  614. database->insertPlayerIntoGameRoom(accountID, gameRoomID);
  615. sendAccountJoinsRoom(targetRoom, accountID);
  616. //No reply to client - will be sent once match server establishes proxy connection with lobby
  617. broadcastActiveGameRooms();
  618. }
  619. void LobbyServer::receiveChangeRoomDescription(const NetworkConnectionPtr & connection, const JsonNode & json)
  620. {
  621. std::string gameRoomID = activeGameRooms[connection];
  622. std::string description = json["description"].String();
  623. database->updateRoomDescription(gameRoomID, description);
  624. broadcastActiveGameRooms();
  625. }
  626. void LobbyServer::receiveGameStarted(const NetworkConnectionPtr & connection, const JsonNode & json)
  627. {
  628. std::string gameRoomID = activeGameRooms[connection];
  629. database->setGameRoomStatus(gameRoomID, LobbyRoomState::BUSY);
  630. broadcastActiveGameRooms();
  631. }
  632. void LobbyServer::receiveLeaveGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json)
  633. {
  634. std::string accountID = json["accountID"].String();
  635. std::string gameRoomID = activeGameRooms[connection];
  636. if(!database->isPlayerInGameRoom(accountID, gameRoomID))
  637. return sendOperationFailed(connection, "You are not in the room!");
  638. database->deletePlayerFromGameRoom(accountID, gameRoomID);
  639. broadcastActiveGameRooms();
  640. }
  641. void LobbyServer::receiveSendInvite(const NetworkConnectionPtr & connection, const JsonNode & json)
  642. {
  643. std::string senderName = activeAccounts[connection];
  644. std::string accountID = json["accountID"].String();
  645. std::string gameRoomID = database->getAccountGameRoom(senderName);
  646. auto targetAccountConnection = findAccount(accountID);
  647. if(!targetAccountConnection)
  648. return sendOperationFailed(connection, "Player is offline or does not exists!");
  649. if(!database->isPlayerInGameRoom(senderName))
  650. return sendOperationFailed(connection, "You are not in the room!");
  651. if(database->isPlayerInGameRoom(accountID))
  652. return sendOperationFailed(connection, "This player is already in a room!");
  653. if(database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::NOT_INVITED)
  654. return sendOperationFailed(connection, "This player is already invited!");
  655. database->insertGameRoomInvite(accountID, gameRoomID);
  656. sendInviteReceived(targetAccountConnection, senderName, gameRoomID);
  657. broadcastActiveGameRooms();
  658. }
  659. LobbyServer::~LobbyServer() = default;
  660. LobbyServer::LobbyServer(const boost::filesystem::path & databasePath)
  661. : database(std::make_unique<LobbyDatabase>(databasePath))
  662. , networkHandler(INetworkHandler::createHandler())
  663. , networkServer(networkHandler->createServerTCP(*this))
  664. {
  665. }
  666. void LobbyServer::start(uint16_t port)
  667. {
  668. networkServer->start(port);
  669. }
  670. void LobbyServer::run()
  671. {
  672. networkHandler->run();
  673. }