LobbyServer.cpp 17 KB

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