LobbyServer.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  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/JsonNode.h"
  14. #include <boost/uuid/uuid_generators.hpp>
  15. #include <boost/uuid/uuid_io.hpp>
  16. static const auto accountCookieLifetime = std::chrono::hours(24 * 7);
  17. bool LobbyServer::isAccountNameValid(const std::string & accountName) const
  18. {
  19. if(accountName.size() < 4)
  20. return false;
  21. if(accountName.size() < 20)
  22. return false;
  23. for(const auto & c : accountName)
  24. if(!std::isalnum(c))
  25. return false;
  26. return true;
  27. }
  28. std::string LobbyServer::sanitizeChatMessage(const std::string & inputString) const
  29. {
  30. // TODO: sanitize message and remove any "weird" symbols from it
  31. return inputString;
  32. }
  33. NetworkConnectionPtr LobbyServer::findAccount(const std::string & accountID) const
  34. {
  35. for(const auto & account : activeAccounts)
  36. if(account.second == accountID)
  37. return account.first;
  38. return nullptr;
  39. }
  40. NetworkConnectionPtr LobbyServer::findGameRoom(const std::string & gameRoomID) const
  41. {
  42. for(const auto & account : activeGameRooms)
  43. if(account.second == gameRoomID)
  44. return account.first;
  45. return nullptr;
  46. }
  47. void LobbyServer::sendMessage(const NetworkConnectionPtr & target, const JsonNode & json)
  48. {
  49. networkServer->sendPacket(target, json.toBytes(true));
  50. }
  51. void LobbyServer::sendAccountCreated(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & accountCookie)
  52. {
  53. JsonNode reply;
  54. reply["type"].String() = "accountCreated";
  55. reply["accountID"].String() = accountID;
  56. reply["accountCookie"].String() = accountCookie;
  57. sendMessage(target, reply);
  58. }
  59. void LobbyServer::sendInviteReceived(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & gameRoomID)
  60. {
  61. JsonNode reply;
  62. reply["type"].String() = "inviteReceived";
  63. reply["accountID"].String() = accountID;
  64. reply["gameRoomID"].String() = gameRoomID;
  65. sendMessage(target, reply);
  66. }
  67. void LobbyServer::sendOperationFailed(const NetworkConnectionPtr & target, const std::string & reason)
  68. {
  69. JsonNode reply;
  70. reply["type"].String() = "operationFailed";
  71. reply["reason"].String() = reason;
  72. sendMessage(target, reply);
  73. }
  74. void LobbyServer::sendLoginSuccess(const NetworkConnectionPtr & target, const std::string & accountCookie, const std::string & displayName)
  75. {
  76. JsonNode reply;
  77. reply["type"].String() = "loginSuccess";
  78. reply["accountCookie"].String() = accountCookie;
  79. if(!displayName.empty())
  80. reply["displayName"].String() = displayName;
  81. sendMessage(target, reply);
  82. }
  83. void LobbyServer::sendChatHistory(const NetworkConnectionPtr & target, const std::vector<LobbyChatMessage> & history)
  84. {
  85. JsonNode reply;
  86. reply["type"].String() = "chatHistory";
  87. for(const auto & message : boost::adaptors::reverse(history))
  88. {
  89. JsonNode jsonEntry;
  90. jsonEntry["accountID"].String() = message.accountID;
  91. jsonEntry["displayName"].String() = message.displayName;
  92. jsonEntry["messageText"].String() = message.messageText;
  93. jsonEntry["ageSeconds"].Integer() = message.age.count();
  94. reply["messages"].Vector().push_back(jsonEntry);
  95. }
  96. sendMessage(target, reply);
  97. }
  98. void LobbyServer::broadcastActiveAccounts()
  99. {
  100. auto activeAccountsStats = database->getActiveAccounts();
  101. JsonNode reply;
  102. reply["type"].String() = "activeAccounts";
  103. for(const auto & account : activeAccountsStats)
  104. {
  105. JsonNode jsonEntry;
  106. jsonEntry["accountID"].String() = account.accountID;
  107. jsonEntry["displayName"].String() = account.displayName;
  108. jsonEntry["status"].String() = "In Lobby"; // TODO: in room status, in match status, offline status(?)
  109. reply["accounts"].Vector().push_back(jsonEntry);
  110. }
  111. for(const auto & connection : activeAccounts)
  112. sendMessage(connection.first, reply);
  113. }
  114. JsonNode LobbyServer::prepareActiveGameRooms()
  115. {
  116. auto activeGameRoomStats = database->getActiveGameRooms();
  117. JsonNode reply;
  118. reply["type"].String() = "activeGameRooms";
  119. for(const auto & gameRoom : activeGameRoomStats)
  120. {
  121. JsonNode jsonEntry;
  122. jsonEntry["gameRoomID"].String() = gameRoom.roomID;
  123. jsonEntry["hostAccountID"].String() = gameRoom.hostAccountID;
  124. jsonEntry["hostAccountDisplayName"].String() = gameRoom.hostAccountDisplayName;
  125. jsonEntry["description"].String() = "TODO: ROOM DESCRIPTION";
  126. jsonEntry["playersCount"].Integer() = gameRoom.playersCount;
  127. jsonEntry["playersLimit"].Integer() = gameRoom.playersLimit;
  128. reply["gameRooms"].Vector().push_back(jsonEntry);
  129. }
  130. return reply;
  131. }
  132. void LobbyServer::broadcastActiveGameRooms()
  133. {
  134. auto reply = prepareActiveGameRooms();
  135. for(const auto & connection : activeAccounts)
  136. sendMessage(connection.first, reply);
  137. }
  138. void LobbyServer::sendAccountJoinsRoom(const NetworkConnectionPtr & target, const std::string & accountID)
  139. {
  140. JsonNode reply;
  141. reply["type"].String() = "accountJoinsRoom";
  142. reply["accountID"].String() = accountID;
  143. sendMessage(target, reply);
  144. }
  145. void LobbyServer::sendJoinRoomSuccess(const NetworkConnectionPtr & target, const std::string & gameRoomID, bool proxyMode)
  146. {
  147. JsonNode reply;
  148. reply["type"].String() = "joinRoomSuccess";
  149. reply["gameRoomID"].String() = gameRoomID;
  150. reply["proxyMode"].Bool() = proxyMode;
  151. sendMessage(target, reply);
  152. }
  153. 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)
  154. {
  155. JsonNode reply;
  156. reply["type"].String() = "chatMessage";
  157. reply["messageText"].String() = messageText;
  158. reply["accountID"].String() = accountID;
  159. reply["displayName"].String() = displayName;
  160. reply["roomMode"].String() = roomMode;
  161. reply["roomName"].String() = roomName;
  162. sendMessage(target, reply);
  163. }
  164. void LobbyServer::onNewConnection(const NetworkConnectionPtr & connection)
  165. {
  166. // no-op - waiting for incoming data
  167. }
  168. void LobbyServer::onDisconnected(const NetworkConnectionPtr & connection, const std::string & errorMessage)
  169. {
  170. if(activeAccounts.count(connection))
  171. database->setAccountOnline(activeAccounts.at(connection), false);
  172. if(activeGameRooms.count(connection))
  173. database->setGameRoomStatus(activeGameRooms.at(connection), LobbyRoomState::CLOSED);
  174. // NOTE: lost connection can be in only one of these lists (or in none of them)
  175. // calling on all possible containers since calling std::map::erase() with non-existing key is legal
  176. activeAccounts.erase(connection);
  177. activeProxies.erase(connection);
  178. activeGameRooms.erase(connection);
  179. broadcastActiveAccounts();
  180. broadcastActiveGameRooms();
  181. }
  182. void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, const std::vector<std::byte> & message)
  183. {
  184. // proxy connection - no processing, only redirect
  185. if(activeProxies.count(connection))
  186. {
  187. auto lockedPtr = activeProxies.at(connection).lock();
  188. if(lockedPtr)
  189. return lockedPtr->sendPacket(message);
  190. logGlobal->info("Received unexpected message for inactive proxy!");
  191. }
  192. JsonNode json(message.data(), message.size());
  193. // TODO: check for json parsing errors
  194. // TODO: validate json based on received message type
  195. std::string messageType = json["type"].String();
  196. // communication messages from vcmiclient
  197. if(activeAccounts.count(connection))
  198. {
  199. std::string accountName = activeAccounts.at(connection);
  200. logGlobal->info("%s: Received message of type %s", accountName, messageType);
  201. if(messageType == "sendChatMessage")
  202. return receiveSendChatMessage(connection, json);
  203. if(messageType == "openGameRoom")
  204. return receiveOpenGameRoom(connection, json);
  205. if(messageType == "joinGameRoom")
  206. return receiveJoinGameRoom(connection, json);
  207. if(messageType == "sendInvite")
  208. return receiveSendInvite(connection, json);
  209. if(messageType == "declineInvite")
  210. return receiveDeclineInvite(connection, json);
  211. logGlobal->warn("%s: Unknown message type: %s", accountName, messageType);
  212. return;
  213. }
  214. // communication messages from vcmiserver
  215. if(activeGameRooms.count(connection))
  216. {
  217. std::string roomName = activeGameRooms.at(connection);
  218. logGlobal->info("%s: Received message of type %s", roomName, messageType);
  219. if(messageType == "leaveGameRoom")
  220. return receiveLeaveGameRoom(connection, json);
  221. logGlobal->warn("%s: Unknown message type: %s", roomName, messageType);
  222. return;
  223. }
  224. logGlobal->info("(unauthorised): Received message of type %s", messageType);
  225. // unauthorized connections - permit only login or register attempts
  226. if(messageType == "clientLogin")
  227. return receiveClientLogin(connection, json);
  228. if(messageType == "clientRegister")
  229. return receiveClientRegister(connection, json);
  230. if(messageType == "serverLogin")
  231. return receiveServerLogin(connection, json);
  232. if(messageType == "clientProxyLogin")
  233. return receiveClientProxyLogin(connection, json);
  234. if(messageType == "serverProxyLogin")
  235. return receiveServerProxyLogin(connection, json);
  236. // TODO: add logging of suspicious connections.
  237. networkServer->closeConnection(connection);
  238. logGlobal->info("(unauthorised): Unknown message type %s", messageType);
  239. }
  240. void LobbyServer::receiveSendChatMessage(const NetworkConnectionPtr & connection, const JsonNode & json)
  241. {
  242. std::string accountID = activeAccounts[connection];
  243. std::string messageText = json["messageText"].String();
  244. std::string messageTextClean = sanitizeChatMessage(messageText);
  245. std::string displayName = database->getAccountDisplayName(accountID);
  246. if(messageTextClean.empty())
  247. return sendOperationFailed(connection, "No printable characters in sent message!");
  248. database->insertChatMessage(accountID, "global", "english", messageText);
  249. for(const auto & otherConnection : activeAccounts)
  250. sendChatMessage(otherConnection.first, "global", "english", accountID, displayName, messageText);
  251. }
  252. void LobbyServer::receiveClientRegister(const NetworkConnectionPtr & connection, const JsonNode & json)
  253. {
  254. std::string displayName = json["displayName"].String();
  255. std::string language = json["language"].String();
  256. if(isAccountNameValid(displayName))
  257. return sendOperationFailed(connection, "Illegal account name");
  258. if(database->isAccountNameExists(displayName))
  259. return sendOperationFailed(connection, "Account name already in use");
  260. std::string accountCookie = boost::uuids::to_string(boost::uuids::random_generator()());
  261. std::string accountID = boost::uuids::to_string(boost::uuids::random_generator()());
  262. database->insertAccount(accountID, displayName);
  263. database->insertAccessCookie(accountID, accountCookie);
  264. sendAccountCreated(connection, accountID, accountCookie);
  265. }
  266. void LobbyServer::receiveClientLogin(const NetworkConnectionPtr & connection, const JsonNode & json)
  267. {
  268. std::string accountID = json["accountID"].String();
  269. std::string accountCookie = json["accountCookie"].String();
  270. std::string language = json["language"].String();
  271. std::string version = json["version"].String();
  272. if(!database->isAccountIDExists(accountID))
  273. return sendOperationFailed(connection, "Account not found");
  274. auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie, accountCookieLifetime);
  275. if(clientCookieStatus == LobbyCookieStatus::INVALID)
  276. return sendOperationFailed(connection, "Authentification failure");
  277. // prolong existing cookie
  278. database->updateAccessCookie(accountID, accountCookie);
  279. database->updateAccountLoginTime(accountID);
  280. database->setAccountOnline(accountID, true);
  281. std::string displayName = database->getAccountDisplayName(accountID);
  282. activeAccounts[connection] = accountID;
  283. sendLoginSuccess(connection, accountCookie, displayName);
  284. sendChatHistory(connection, database->getRecentMessageHistory());
  285. // send active accounts list to new account
  286. // and update acount list to everybody else
  287. broadcastActiveAccounts();
  288. sendMessage(connection, prepareActiveGameRooms());
  289. }
  290. void LobbyServer::receiveServerLogin(const NetworkConnectionPtr & connection, const JsonNode & json)
  291. {
  292. std::string gameRoomID = json["gameRoomID"].String();
  293. std::string accountID = json["accountID"].String();
  294. std::string accountCookie = json["accountCookie"].String();
  295. std::string version = json["version"].String();
  296. auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie, accountCookieLifetime);
  297. if(clientCookieStatus == LobbyCookieStatus::INVALID)
  298. {
  299. sendOperationFailed(connection, "Invalid credentials");
  300. }
  301. else
  302. {
  303. database->insertGameRoom(gameRoomID, accountID);
  304. activeGameRooms[connection] = gameRoomID;
  305. sendLoginSuccess(connection, accountCookie, {});
  306. broadcastActiveGameRooms();
  307. }
  308. }
  309. void LobbyServer::receiveClientProxyLogin(const NetworkConnectionPtr & connection, const JsonNode & json)
  310. {
  311. std::string gameRoomID = json["gameRoomID"].String();
  312. std::string accountID = json["accountID"].String();
  313. std::string accountCookie = json["accountCookie"].String();
  314. auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie, accountCookieLifetime);
  315. if(clientCookieStatus != LobbyCookieStatus::INVALID)
  316. {
  317. for(auto & proxyEntry : awaitingProxies)
  318. {
  319. if(proxyEntry.accountID != accountID)
  320. continue;
  321. if(proxyEntry.roomID != gameRoomID)
  322. continue;
  323. proxyEntry.accountConnection = connection;
  324. auto gameRoomConnection = proxyEntry.roomConnection.lock();
  325. if(gameRoomConnection)
  326. {
  327. activeProxies[gameRoomConnection] = connection;
  328. activeProxies[connection] = gameRoomConnection;
  329. }
  330. return;
  331. }
  332. }
  333. sendOperationFailed(connection, "Invalid credentials");
  334. networkServer->closeConnection(connection);
  335. }
  336. void LobbyServer::receiveServerProxyLogin(const NetworkConnectionPtr & connection, const JsonNode & json)
  337. {
  338. std::string gameRoomID = json["gameRoomID"].String();
  339. std::string guestAccountID = json["guestAccountID"].String();
  340. std::string accountCookie = json["accountCookie"].String();
  341. // FIXME: find host account ID and validate his cookie
  342. //auto clientCookieStatus = database->getAccountCookieStatus(hostAccountID, accountCookie, accountCookieLifetime);
  343. //if(clientCookieStatus != LobbyCookieStatus::INVALID)
  344. {
  345. NetworkConnectionPtr targetAccount = findAccount(guestAccountID);
  346. if(targetAccount == nullptr)
  347. {
  348. sendOperationFailed(connection, "Invalid credentials");
  349. return; // unknown / disconnected account
  350. }
  351. sendJoinRoomSuccess(targetAccount, gameRoomID, true);
  352. AwaitingProxyState proxy;
  353. proxy.accountID = guestAccountID;
  354. proxy.roomID = gameRoomID;
  355. proxy.roomConnection = connection;
  356. awaitingProxies.push_back(proxy);
  357. return;
  358. }
  359. //networkServer->closeConnection(connection);
  360. }
  361. void LobbyServer::receiveOpenGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json)
  362. {
  363. std::string hostAccountID = json["hostAccountID"].String();
  364. std::string accountID = activeAccounts[connection];
  365. if(database->isPlayerInGameRoom(accountID))
  366. return sendOperationFailed(connection, "Player already in the room!");
  367. std::string gameRoomID = database->getIdleGameRoom(hostAccountID);
  368. if(gameRoomID.empty())
  369. return sendOperationFailed(connection, "Failed to find idle server to join!");
  370. std::string roomType = json["roomType"].String();
  371. if(roomType != "public" && roomType != "private")
  372. return sendOperationFailed(connection, "Invalid room type!");
  373. if(roomType == "public")
  374. database->setGameRoomStatus(gameRoomID, LobbyRoomState::PUBLIC);
  375. if(roomType == "private")
  376. database->setGameRoomStatus(gameRoomID, LobbyRoomState::PRIVATE);
  377. // TODO: additional flags / initial settings, e.g. allowCheats
  378. // TODO: connection mode: direct or proxy. For now direct is assumed. Proxy might be needed later, for hosted servers
  379. database->insertPlayerIntoGameRoom(accountID, gameRoomID);
  380. broadcastActiveGameRooms();
  381. sendJoinRoomSuccess(connection, gameRoomID, false);
  382. }
  383. void LobbyServer::receiveJoinGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json)
  384. {
  385. std::string gameRoomID = json["gameRoomID"].String();
  386. std::string accountID = activeAccounts[connection];
  387. if(database->isPlayerInGameRoom(accountID))
  388. return sendOperationFailed(connection, "Player already in the room!");
  389. NetworkConnectionPtr targetRoom = findGameRoom(gameRoomID);
  390. if(targetRoom == nullptr)
  391. return sendOperationFailed(connection, "Failed to find game room to join!");
  392. auto roomStatus = database->getGameRoomStatus(gameRoomID);
  393. if(roomStatus != LobbyRoomState::PRIVATE && roomStatus != LobbyRoomState::PUBLIC)
  394. return sendOperationFailed(connection, "Room does not accepts new players!");
  395. if(roomStatus == LobbyRoomState::PRIVATE)
  396. {
  397. if(database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::INVITED)
  398. return sendOperationFailed(connection, "You are not permitted to join private room without invite!");
  399. }
  400. if(database->getGameRoomFreeSlots(gameRoomID) == 0)
  401. return sendOperationFailed(connection, "Room is already full!");
  402. database->insertPlayerIntoGameRoom(accountID, gameRoomID);
  403. sendAccountJoinsRoom(targetRoom, accountID);
  404. //No reply to client - will be sent once match server establishes proxy connection with lobby
  405. broadcastActiveGameRooms();
  406. }
  407. void LobbyServer::receiveLeaveGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json)
  408. {
  409. std::string gameRoomID = json["gameRoomID"].String();
  410. std::string senderName = activeAccounts[connection];
  411. if(!database->isPlayerInGameRoom(senderName, gameRoomID))
  412. return sendOperationFailed(connection, "You are not in the room!");
  413. broadcastActiveGameRooms();
  414. }
  415. void LobbyServer::receiveSendInvite(const NetworkConnectionPtr & connection, const JsonNode & json)
  416. {
  417. std::string senderName = activeAccounts[connection];
  418. std::string accountID = json["accountID"].String();
  419. std::string gameRoomID = database->getAccountGameRoom(senderName);
  420. auto targetAccount = findAccount(accountID);
  421. if(!targetAccount)
  422. return sendOperationFailed(connection, "Invalid account to invite!");
  423. if(!database->isPlayerInGameRoom(senderName))
  424. return sendOperationFailed(connection, "You are not in the room!");
  425. if(database->isPlayerInGameRoom(accountID))
  426. return sendOperationFailed(connection, "This player is already in a room!");
  427. if(database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::NOT_INVITED)
  428. return sendOperationFailed(connection, "This player is already invited!");
  429. database->insertGameRoomInvite(accountID, gameRoomID);
  430. sendInviteReceived(targetAccount, senderName, gameRoomID);
  431. }
  432. void LobbyServer::receiveDeclineInvite(const NetworkConnectionPtr & connection, const JsonNode & json)
  433. {
  434. std::string accountID = activeAccounts[connection];
  435. std::string gameRoomID = json["gameRoomID"].String();
  436. if(database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::INVITED)
  437. return sendOperationFailed(connection, "No active invite found!");
  438. database->deleteGameRoomInvite(accountID, gameRoomID);
  439. }
  440. LobbyServer::~LobbyServer() = default;
  441. LobbyServer::LobbyServer(const boost::filesystem::path & databasePath)
  442. : database(std::make_unique<LobbyDatabase>(databasePath))
  443. , networkHandler(INetworkHandler::createHandler())
  444. , networkServer(networkHandler->createServerTCP(*this))
  445. {
  446. }
  447. void LobbyServer::start(uint16_t port)
  448. {
  449. networkServer->start(port);
  450. }
  451. void LobbyServer::run()
  452. {
  453. networkHandler->run();
  454. }