void Server::externalJoinGameCommandReceived(const Command_JoinGame &cmd, int cmdId, int roomId, int serverId, qint64 sessionId) { // This function is always called from the main thread via signal/slot. try { QReadLocker roomsLocker(&roomsLock); QReadLocker clientsLocker(&clientsLock); Server_Room *room = rooms.value(roomId); if (!room) { qDebug() << "externalJoinGameCommandReceived: room id=" << roomId << "not found"; throw Response::RespNotInRoom; } Server_AbstractUserInterface *userInterface = externalUsersBySessionId.value(sessionId); if (!userInterface) { qDebug() << "externalJoinGameCommandReceived: session id=" << sessionId << "not found"; throw Response::RespNotInRoom; } ResponseContainer responseContainer(cmdId); Response::ResponseCode responseCode = room->processJoinGameCommand(cmd, responseContainer, userInterface); userInterface->sendResponseContainer(responseContainer, responseCode); } catch (Response::ResponseCode code) { Response response; response.set_cmd_id(cmdId); response.set_response_code(code); sendIsl_Response(response, serverId, sessionId); } }
IslInterface::~IslInterface() { logger->logMessage("[ISL] session ended", this); flushOutputBuffer(); // As these signals are connected with Qt::QueuedConnection implicitly, // we don't need to worry about them modifying the lists while we're iterating. server->roomsLock.lockForRead(); QMapIterator<int, Server_Room *> roomIterator(server->getRooms()); while (roomIterator.hasNext()) { Server_Room *room = roomIterator.next().value(); room->usersLock.lockForRead(); QMapIterator<QString, ServerInfo_User_Container> roomUsers(room->getExternalUsers()); while (roomUsers.hasNext()) { roomUsers.next(); if (roomUsers.value().getUserInfo()->server_id() == serverId) emit externalRoomUserLeft(room->getId(), roomUsers.key()); } room->usersLock.unlock(); } server->roomsLock.unlock(); server->clientsLock.lockForRead(); QMapIterator<QString, Server_AbstractUserInterface *> extUsers(server->getExternalUsers()); while (extUsers.hasNext()) { extUsers.next(); if (extUsers.value()->getUserInfo()->server_id() == serverId) emit externalUserLeft(extUsers.key()); } server->clientsLock.unlock(); }
Response::ResponseCode Server_ProtocolHandler::cmdGetGamesOfUser(const Command_GetGamesOfUser &cmd, ResponseContainer &rc) { if (authState == NotLoggedIn) return Response::RespLoginNeeded; // We don't need to check whether the user is logged in; persistent games should also work. // The client needs to deal with an empty result list. Response_GetGamesOfUser *re = new Response_GetGamesOfUser; server->roomsLock.lockForRead(); QMapIterator<int, Server_Room *> roomIterator(server->getRooms()); while (roomIterator.hasNext()) { Server_Room *room = roomIterator.next().value(); room->gamesLock.lockForRead(); room->getInfo(*re->add_room_list(), false, true); QListIterator<ServerInfo_Game> gameIterator(room->getGamesOfUser(QString::fromStdString(cmd.user_name()))); while (gameIterator.hasNext()) re->add_game_list()->CopyFrom(gameIterator.next()); room->gamesLock.unlock(); } server->roomsLock.unlock(); rc.setResponseExtension(re); return Response::RespOk; }
void Server_AbstractUserInterface::joinPersistentGames(ResponseContainer &rc) { QList<PlayerReference> gamesToJoin = server->getPersistentPlayerReferences(QString::fromStdString(userInfo->name())); server->roomsLock.lockForRead(); for (int i = 0; i < gamesToJoin.size(); ++i) { const PlayerReference &pr = gamesToJoin.at(i); Server_Room *room = server->getRooms().value(pr.getRoomId()); if (!room) continue; QReadLocker roomGamesLocker(&room->gamesLock); Server_Game *game = room->getGames().value(pr.getGameId()); if (!game) continue; QMutexLocker gameLocker(&game->gameMutex); Server_Player *player = game->getPlayers().value(pr.getPlayerId()); player->setUserInterface(this); playerAddedToGame(game->getGameId(), room->getId(), player->getPlayerId()); game->createGameJoinedEvent(player, rc, true); } server->roomsLock.unlock(); }
void Server::externalGameCommandContainerReceived(const CommandContainer &cont, int playerId, int serverId, qint64 sessionId) { // This function is always called from the main thread via signal/slot. try { ResponseContainer responseContainer(cont.cmd_id()); Response::ResponseCode finalResponseCode = Response::RespOk; QReadLocker roomsLocker(&roomsLock); Server_Room *room = rooms.value(cont.room_id()); if (!room) { qDebug() << "externalGameCommandContainerReceived: room id=" << cont.room_id() << "not found"; throw Response::RespNotInRoom; } QReadLocker roomGamesLocker(&room->gamesLock); Server_Game *game = room->getGames().value(cont.game_id()); if (!game) { qDebug() << "externalGameCommandContainerReceived: game id=" << cont.game_id() << "not found"; throw Response::RespNotInRoom; } QMutexLocker gameLocker(&game->gameMutex); Server_Player *player = game->getPlayers().value(playerId); if (!player) { qDebug() << "externalGameCommandContainerReceived: player id=" << playerId << "not found"; throw Response::RespNotInRoom; } GameEventStorage ges; for (int i = cont.game_command_size() - 1; i >= 0; --i) { const GameCommand &sc = cont.game_command(i); qDebug() << "[ISL]" << QString::fromStdString(sc.ShortDebugString()); Response::ResponseCode resp = player->processGameCommand(sc, responseContainer, ges); if (resp != Response::RespOk) finalResponseCode = resp; } ges.sendToGame(game); if (finalResponseCode != Response::RespNothing) { player->playerMutex.lock(); player->getUserInterface()->sendResponseContainer(responseContainer, finalResponseCode); player->playerMutex.unlock(); } } catch (Response::ResponseCode code) { Response response; response.set_cmd_id(cont.cmd_id()); response.set_response_code(code); sendIsl_Response(response, serverId, sessionId); } }
int Server::getGamesCount() const { int result = 0; QReadLocker locker(&roomsLock); QMapIterator<int, Server_Room *> roomIterator(rooms); while (roomIterator.hasNext()) { Server_Room *room = roomIterator.next().value(); QReadLocker roomLocker(&room->gamesLock); result += room->getGames().size(); } return result; }
void Server::externalRoomSay(int roomId, const QString &userName, const QString &message) { // This function is always called from the main thread via signal/slot. QReadLocker locker(&roomsLock); Server_Room *room = rooms.value(roomId); if (!room) { qDebug() << "externalRoomSay: room id=" << roomId << "not found"; return; } room->say(userName, message, false); }
void Server::externalRoomUserLeft(int roomId, const QString &userName) { // This function is always called from the main thread via signal/slot. QReadLocker locker(&roomsLock); Server_Room *room = rooms.value(roomId); if (!room) { qDebug() << "externalRoomUserLeft: room id=" << roomId << "not found"; return; } room->removeExternalUser(userName); }
void Server::externalRoomUserJoined(int roomId, const ServerInfo_User &userInfo) { // This function is always called from the main thread via signal/slot. QReadLocker locker(&roomsLock); Server_Room *room = rooms.value(roomId); if (!room) { qDebug() << "externalRoomUserJoined: room id=" << roomId << "not found"; return; } room->addExternalUser(userInfo); }
Response::ResponseCode Server_ProtocolHandler::cmdJoinRoom(const Command_JoinRoom &cmd, ResponseContainer &rc) { if (authState == NotLoggedIn) return Response::RespLoginNeeded; if (rooms.contains(cmd.room_id())) return Response::RespContextError; QReadLocker serverLocker(&server->roomsLock); Server_Room *r = server->getRooms().value(cmd.room_id(), 0); if (!r) return Response::RespNameNotFound; QString roomPermission = r->getRoomPermission().toLower(); if (roomPermission != "none"){ if (roomPermission == "registered") { if (!(userInfo->user_level() & ServerInfo_User::IsRegistered)) return Response::RespUserLevelTooLow; } if (roomPermission == "moderator"){ if (!(userInfo->user_level() & ServerInfo_User::IsModerator)) return Response::RespUserLevelTooLow; } if (roomPermission == "administrator"){ if (!(userInfo->user_level() & ServerInfo_User::IsAdmin)) return Response::RespUserLevelTooLow; } } r->addClient(this); rooms.insert(r->getId(), r); Event_RoomSay joinMessageEvent; joinMessageEvent.set_message(r->getJoinMessage().toStdString()); joinMessageEvent.set_message_type(Event_RoomSay::Welcome); rc.enqueuePostResponseItem(ServerMessage::ROOM_EVENT, r->prepareRoomEvent(joinMessageEvent)); QReadLocker chatHistoryLocker(&r->historyLock); QList<ServerInfo_ChatMessage> chatHistory = r->getChatHistory(); ServerInfo_ChatMessage chatMessage; for (int i = 0; i < chatHistory.size(); ++i) { chatMessage = chatHistory.at(i); qDebug() << QString::fromStdString(chatMessage.message()).simplified(); Event_RoomSay roomChatHistory; roomChatHistory.set_message(chatMessage.sender_name() + ": " + chatMessage.message()); roomChatHistory.set_message_type(Event_RoomSay::ChatHistory); roomChatHistory.set_time_of(QDateTime::fromString(QString::fromStdString(chatMessage.time())).toMSecsSinceEpoch()); rc.enqueuePostResponseItem(ServerMessage::ROOM_EVENT, r->prepareRoomEvent(roomChatHistory)); } Response_JoinRoom *re = new Response_JoinRoom; r->getInfo(*re->mutable_room_info(), true); rc.setResponseExtension(re); return Response::RespOk; }
void Server::externalRoomGameListChanged(int roomId, const ServerInfo_Game &gameInfo) { // This function is always called from the main thread via signal/slot. QReadLocker locker(&roomsLock); Server_Room *room = rooms.value(roomId); if (!room) { qDebug() << "externalRoomGameListChanged: room id=" << roomId << "not found"; return; } room->updateExternalGameList(gameInfo); }
void Server::externalRoomSay(int roomId, const QString &userName, const QString &message) { // This function is always called from the main thread via signal/slot. QReadLocker locker(&roomsLock); Server_Room *room = rooms.value(roomId); if (!room) { qDebug() << "externalRoomSay: room id=" << roomId << "not found"; return; } room->say(userName, message, false); getDatabaseInterface()->logMessage(0, userName, "ISL", message, Server_DatabaseInterface::MessageTargetIslRoom, room->getId(), room->getName()); }
Response::ResponseCode Server_ProtocolHandler::processGameCommandContainer(const CommandContainer &cont, ResponseContainer &rc) { if (authState == NotLoggedIn) return Response::RespLoginNeeded; QMap<int, QPair<int, int> > gameMap = getGames(); if (!gameMap.contains(cont.game_id())) return Response::RespNotInRoom; const QPair<int, int> roomIdAndPlayerId = gameMap.value(cont.game_id()); QReadLocker roomsLocker(&server->roomsLock); Server_Room *room = server->getRooms().value(roomIdAndPlayerId.first); if (!room) return Response::RespNotInRoom; QReadLocker roomGamesLocker(&room->gamesLock); Server_Game *game = room->getGames().value(cont.game_id()); if (!game) { if (room->getExternalGames().contains(cont.game_id())) { server->sendIsl_GameCommand(cont, room->getExternalGames().value(cont.game_id()).server_id(), userInfo->session_id(), roomIdAndPlayerId.first, roomIdAndPlayerId.second ); return Response::RespNothing; } return Response::RespNotInRoom; } QMutexLocker gameLocker(&game->gameMutex); Server_Player *player = game->getPlayers().value(roomIdAndPlayerId.second); if (!player) return Response::RespNotInRoom; GameEventStorage ges; Response::ResponseCode finalResponseCode = Response::RespOk; for (int i = cont.game_command_size() - 1; i >= 0; --i) { const GameCommand &sc = cont.game_command(i); logDebugMessage(QString("game %1 player %2: ").arg(cont.game_id()).arg(roomIdAndPlayerId.second) + QString::fromStdString(sc.ShortDebugString())); Response::ResponseCode resp = player->processGameCommand(sc, rc, ges); if (resp != Response::RespOk) finalResponseCode = resp; } ges.sendToGame(game); return finalResponseCode; }
void Server::externalUserLeft(const QString &userName) { // This function is always called from the main thread via signal/slot. clientsLock.lockForWrite(); Server_AbstractUserInterface *user = externalUsers.take(userName); externalUsersBySessionId.remove(user->getUserInfo()->session_id()); clientsLock.unlock(); QMap<int, QPair<int, int> > userGames(user->getGames()); QMapIterator<int, QPair<int, int> > userGamesIterator(userGames); roomsLock.lockForRead(); while (userGamesIterator.hasNext()) { userGamesIterator.next(); Server_Room *room = rooms.value(userGamesIterator.value().first); if (!room) continue; QReadLocker roomGamesLocker(&room->gamesLock); Server_Game *game = room->getGames().value(userGamesIterator.key()); if (!game) continue; QMutexLocker gameLocker(&game->gameMutex); Server_Player *player = game->getPlayers().value(userGamesIterator.value().second); if (!player) continue; player->disconnectClient(); } roomsLock.unlock(); delete user; Event_UserLeft event; event.set_name(userName.toStdString()); SessionEvent *se = Server_ProtocolHandler::prepareSessionEvent(event); clientsLock.lockForRead(); for (int i = 0; i < clients.size(); ++i) if (clients[i]->getAcceptsUserListChanges()) clients[i]->sendProtocolItem(*se); clientsLock.unlock(); delete se; }
// This function must only be called from the thread this object lives in. // The thread must not hold any server locks when calling this (e.g. clientsLock, roomsLock). void Server_ProtocolHandler::prepareDestroy() { if (deleted) return; deleted = true; QMapIterator<int, Server_Room *> roomIterator(rooms); while (roomIterator.hasNext()) roomIterator.next().value()->removeClient(this); QMap<int, QPair<int, int> > tempGames(getGames()); server->roomsLock.lockForRead(); QMapIterator<int, QPair<int, int> > gameIterator(tempGames); while (gameIterator.hasNext()) { gameIterator.next(); Server_Room *r = server->getRooms().value(gameIterator.value().first); if (!r) continue; r->gamesLock.lockForRead(); Server_Game *g = r->getGames().value(gameIterator.key()); if (!g) { r->gamesLock.unlock(); continue; } g->gameMutex.lock(); Server_Player *p = g->getPlayers().value(gameIterator.value().second); if (!p) { g->gameMutex.unlock(); r->gamesLock.unlock(); continue; } p->disconnectClient(); g->gameMutex.unlock(); r->gamesLock.unlock(); } server->roomsLock.unlock(); server->removeClient(this); deleteLater(); }
Response::ResponseCode Server_ProtocolHandler::cmdJoinRoom(const Command_JoinRoom &cmd, ResponseContainer &rc) { if (authState == NotLoggedIn) return Response::RespLoginNeeded; if (rooms.contains(cmd.room_id())) return Response::RespContextError; QReadLocker serverLocker(&server->roomsLock); Server_Room *r = server->getRooms().value(cmd.room_id(), 0); if (!r) return Response::RespNameNotFound; r->addClient(this); rooms.insert(r->getId(), r); Event_RoomSay joinMessageEvent; joinMessageEvent.set_message(r->getJoinMessage().toStdString()); rc.enqueuePostResponseItem(ServerMessage::ROOM_EVENT, r->prepareRoomEvent(joinMessageEvent)); Response_JoinRoom *re = new Response_JoinRoom; r->getInfo(*re->mutable_room_info(), true); rc.setResponseExtension(re); return Response::RespOk; }
Response::ResponseCode Server_ProtocolHandler::cmdJoinRoom(const Command_JoinRoom &cmd, ResponseContainer &rc) { if (authState == NotLoggedIn) return Response::RespLoginNeeded; if (rooms.contains(cmd.room_id())) return Response::RespContextError; QReadLocker serverLocker(&server->roomsLock); Server_Room *r = server->getRooms().value(cmd.room_id(), 0); if (!r) return Response::RespNameNotFound; QString roomPermission = r->getRoomPermission().toLower(); if (roomPermission != "none"){ if (roomPermission == "registered") { if (!(userInfo->user_level() & ServerInfo_User::IsRegistered)) return Response::RespUserLevelTooLow; } if (roomPermission == "moderator"){ if (!(userInfo->user_level() & ServerInfo_User::IsModerator)) return Response::RespUserLevelTooLow; } if (roomPermission == "administrator"){ if (!(userInfo->user_level() & ServerInfo_User::IsAdmin)) return Response::RespUserLevelTooLow; } } r->addClient(this); rooms.insert(r->getId(), r); Event_RoomSay joinMessageEvent; joinMessageEvent.set_message(r->getJoinMessage().toStdString()); rc.enqueuePostResponseItem(ServerMessage::ROOM_EVENT, r->prepareRoomEvent(joinMessageEvent)); Response_JoinRoom *re = new Response_JoinRoom; r->getInfo(*re->mutable_room_info(), true); rc.setResponseExtension(re); return Response::RespOk; }
ResponseCode Server_ProtocolHandler::cmdJoinRoom(Command_JoinRoom *cmd, CommandContainer *cont) { if (authState == PasswordWrong) return RespLoginNeeded; if (rooms.contains(cmd->getRoomId())) return RespContextError; Server_Room *r = server->getRooms().value(cmd->getRoomId(), 0); if (!r) return RespNameNotFound; r->addClient(this); rooms.insert(r->getId(), r); enqueueProtocolItem(new Event_RoomSay(r->getId(), QString(), r->getJoinMessage())); cont->setResponse(new Response_JoinRoom(cont->getCmdId(), RespOk, r->getInfo(true))); return RespNothing; }
Response::ResponseCode Server_ProtocolHandler::processGameCommandContainer(const CommandContainer &cont, ResponseContainer &rc) { static QList<GameCommand::GameCommandType> antifloodCommandsWhiteList = QList<GameCommand::GameCommandType>() // draw/undo card draw (example: drawing 10 cards one by one from the deck) << GameCommand::DRAW_CARDS << GameCommand::UNDO_DRAW // create, delete arrows (example: targeting with 10 cards during an attack) << GameCommand::CREATE_ARROW << GameCommand::DELETE_ARROW // set card attributes (example: tapping 10 cards at once) << GameCommand::SET_CARD_ATTR // increment / decrement counter (example: -10 life points one by one) << GameCommand::INC_COUNTER // mulling lots of hands in a row << GameCommand::MULLIGAN // allows a user to sideboard without receiving flooding message << GameCommand::MOVE_CARD; if (authState == NotLoggedIn) return Response::RespLoginNeeded; QMap<int, QPair<int, int> > gameMap = getGames(); if (!gameMap.contains(cont.game_id())) return Response::RespNotInRoom; const QPair<int, int> roomIdAndPlayerId = gameMap.value(cont.game_id()); QReadLocker roomsLocker(&server->roomsLock); Server_Room *room = server->getRooms().value(roomIdAndPlayerId.first); if (!room) return Response::RespNotInRoom; QReadLocker roomGamesLocker(&room->gamesLock); Server_Game *game = room->getGames().value(cont.game_id()); if (!game) { if (room->getExternalGames().contains(cont.game_id())) { server->sendIsl_GameCommand(cont, room->getExternalGames().value(cont.game_id()).server_id(), userInfo->session_id(), roomIdAndPlayerId.first, roomIdAndPlayerId.second ); return Response::RespNothing; } return Response::RespNotInRoom; } QMutexLocker gameLocker(&game->gameMutex); Server_Player *player = game->getPlayers().value(roomIdAndPlayerId.second); if (!player) return Response::RespNotInRoom; int commandCountingInterval = server->getCommandCountingInterval(); int maxCommandCountPerInterval = server->getMaxCommandCountPerInterval(); GameEventStorage ges; Response::ResponseCode finalResponseCode = Response::RespOk; for (int i = cont.game_command_size() - 1; i >= 0; --i) { const GameCommand &sc = cont.game_command(i); logDebugMessage(QString("game %1 player %2: ").arg(cont.game_id()).arg(roomIdAndPlayerId.second) + QString::fromStdString(sc.ShortDebugString())); if (commandCountingInterval > 0) { int totalCount = 0; if (commandCountOverTime.isEmpty()) commandCountOverTime.prepend(0); if(!antifloodCommandsWhiteList.contains((GameCommand::GameCommandType) getPbExtension(sc))) ++commandCountOverTime[0]; for (int i = 0; i < commandCountOverTime.size(); ++i) totalCount += commandCountOverTime[i]; if (totalCount > maxCommandCountPerInterval) return Response::RespChatFlood; } Response::ResponseCode resp = player->processGameCommand(sc, rc, ges); if (resp != Response::RespOk) finalResponseCode = resp; } ges.sendToGame(game); return finalResponseCode; }
void IslInterface::initServer() { socket->setSocketDescriptor(socketDescriptor); logger->logMessage(QString("[ISL] incoming connection: %1").arg(socket->peerAddress().toString())); QList<ServerProperties> serverList = server->getServerList(); int listIndex = -1; for (int i = 0; i < serverList.size(); ++i) if (serverList[i].address == socket->peerAddress()) { listIndex = i; break; } if (listIndex == -1) { logger->logMessage(QString("[ISL] address %1 unknown, terminating connection").arg(socket->peerAddress().toString())); deleteLater(); return; } socket->startServerEncryption(); if (!socket->waitForEncrypted(5000)) { QList<QSslError> sslErrors(socket->sslErrors()); if (sslErrors.isEmpty()) qDebug() << "[ISL] SSL handshake timeout, terminating connection"; else qDebug() << "[ISL] SSL errors:" << sslErrors; deleteLater(); return; } if (serverList[listIndex].cert == socket->peerCertificate()) logger->logMessage(QString("[ISL] Peer authenticated as " + serverList[listIndex].hostname)); else { logger->logMessage(QString("[ISL] Authentication failed, terminating connection")); deleteLater(); return; } serverId = serverList[listIndex].id; Event_ServerCompleteList event; event.set_server_id(server->getServerId()); server->clientsLock.lockForRead(); QMapIterator<QString, Server_ProtocolHandler *> userIterator(server->getUsers()); while (userIterator.hasNext()) event.add_user_list()->CopyFrom(userIterator.next().value()->copyUserInfo(true, true)); server->clientsLock.unlock(); server->roomsLock.lockForRead(); QMapIterator<int, Server_Room *> roomIterator(server->getRooms()); while (roomIterator.hasNext()) { Server_Room *room = roomIterator.next().value(); room->usersLock.lockForRead(); room->gamesLock.lockForRead(); room->getInfo(*event.add_room_list(), true, true, false, false); } IslMessage message; message.set_message_type(IslMessage::SESSION_EVENT); SessionEvent *sessionEvent = message.mutable_session_event(); sessionEvent->GetReflection()->MutableMessage(sessionEvent, event.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(event); server->islLock.lockForWrite(); if (server->islConnectionExists(serverId)) { qDebug() << "[ISL] Duplicate connection to #" << serverId << "terminating connection"; deleteLater(); } else { transmitMessage(message); server->addIslInterface(serverId, this); } server->islLock.unlock(); roomIterator.toFront(); while (roomIterator.hasNext()) { roomIterator.next(); roomIterator.value()->gamesLock.unlock(); roomIterator.value()->usersLock.unlock(); } server->roomsLock.unlock(); }