diff options
-rw-r--r-- | ChangeLog | 17 | ||||
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/account-server/accountclient.cpp | 3 | ||||
-rw-r--r-- | src/account-server/accountclient.hpp | 9 | ||||
-rw-r--r-- | src/account-server/accounthandler.cpp | 582 | ||||
-rw-r--r-- | src/account-server/accounthandler.hpp | 48 | ||||
-rw-r--r-- | src/account-server/serverhandler.cpp | 7 | ||||
-rw-r--r-- | src/account-server/serverhandler.hpp | 2 | ||||
-rw-r--r-- | src/defines.h | 3 | ||||
-rw-r--r-- | src/game-server/accountconnection.cpp | 5 | ||||
-rw-r--r-- | src/game-server/gamehandler.cpp | 156 | ||||
-rw-r--r-- | src/game-server/gamehandler.hpp | 50 | ||||
-rw-r--r-- | src/net/netcomputer.cpp | 9 | ||||
-rw-r--r-- | src/utils/tokencollector.hpp | 326 |
14 files changed, 828 insertions, 391 deletions
@@ -1,3 +1,20 @@ +2007-03-18 Rogier Polak <rogier.l.a.polak@gmail.com> + + * src/net/netcomputer.cpp: Corrected the debug message for big-endian + architectures. + * src/defines.h, src/Makefile.am, + src/account-server/accountclient.hpp, + src/account-server/accountclient.cpp, + src/account-server/accounthandler.hpp, + src/account-server/accounthandler.cpp, + src/account-server/serverhandler.hpp, + src/account-server/serverhandler.cpp, + src/game-server/accountconnection.cpp, + src/game-server/gamehandler.hpp, src/game-server/gamehandler.cpp, + src/utils/tokencollector.hpp: Added a TokenCollector class, which + matches tokens used for moving clients between servers. Improved the + handling of connecting clients for the account-server. + 2007-03-15 Philipp Sehmisch <tmw@crushnet.org> * src/account-server/accounthandler.cpp: Set the default map diff --git a/src/Makefile.am b/src/Makefile.am index 6deadc84..3298bdf0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -60,6 +60,7 @@ tmwserv_account_SOURCES = \ utils/processorutils.cpp \ utils/stringfilter.h \ utils/stringfilter.cpp \ + utils/tokencollector.hpp \ utils/tokendispenser.hpp \ utils/tokendispenser.cpp @@ -130,6 +131,7 @@ tmwserv_game_SOURCES = \ utils/stringfilter.cpp \ utils/timer.h \ utils/timer.cpp \ + utils/tokencollector.hpp \ utils/tokendispenser.hpp \ utils/tokendispenser.cpp \ utils/xml.hpp \ diff --git a/src/account-server/accountclient.cpp b/src/account-server/accountclient.cpp index 8be44289..c17e95a8 100644 --- a/src/account-server/accountclient.cpp +++ b/src/account-server/accountclient.cpp @@ -30,7 +30,8 @@ AccountClient::AccountClient(ENetPeer *peer): NetComputer(peer), mAccountPtr(NULL), - mCharacterPtr(NULL) + mCharacterPtr(NULL), + status(CLIENT_LOGIN) { } diff --git a/src/account-server/accountclient.hpp b/src/account-server/accountclient.hpp index eb88016c..a28c46dd 100644 --- a/src/account-server/accountclient.hpp +++ b/src/account-server/accountclient.hpp @@ -32,6 +32,13 @@ class AccountHandler; +enum +{ + CLIENT_LOGIN = 0, + CLIENT_CONNECTED, + CLIENT_QUEQUED +}; + /** * A connected computer that can have an account and character associated with * it. @@ -85,6 +92,8 @@ class AccountClient : public NetComputer CharacterPtr getCharacter() const { return mCharacterPtr; } + int status; + private: /** Account associated with connection */ AccountPtr mAccountPtr; diff --git a/src/account-server/accounthandler.cpp b/src/account-server/accounthandler.cpp index a77516ce..423cd586 100644 --- a/src/account-server/accounthandler.cpp +++ b/src/account-server/accounthandler.cpp @@ -38,26 +38,13 @@ #include "net/netcomputer.hpp" #include "utils/logger.h" #include "utils/stringfilter.h" +#include "utils/tokencollector.hpp" #include "utils/tokendispenser.hpp" -typedef std::map< std::string, AccountClient* > AccountPendingClients; -typedef std::map< std::string, int > AccountPendingReconnects; - -/** - * Client is faster then game server - */ -static AccountPendingClients pendingClients; - -/** - * Game server is faster then client - */ -static AccountPendingReconnects pendingReconnects; - -void -registerAccountReconnect(int accountID, const std::string& magic_token); - -void -handleReconnectedAccount(AccountClient &computer, int accountID); +AccountHandler::AccountHandler(): + mTokenCollector(this) +{ +} bool AccountHandler::startListen(enet_uint16 port) @@ -75,7 +62,13 @@ AccountHandler::computerConnected(ENetPeer *peer) void AccountHandler::computerDisconnected(NetComputer *comp) { - delete comp; + AccountClient* computer = static_cast< AccountClient * >(comp); + + if (computer->status == CLIENT_QUEQUED) + // Delete it from the pendingClient list + mTokenCollector.deletePendingClient(computer); + + delete computer; // ~AccountClient unsets the account } /** @@ -99,8 +92,6 @@ AccountHandler::processMessage(NetComputer *comp, MessageIn &message) store.open(); #endif - MessageOut result; - switch (message.getId()) { case PAMSG_LOGIN: @@ -129,50 +120,13 @@ AccountHandler::processMessage(NetComputer *comp, MessageIn &message) break; case PAMSG_EMAIL_CHANGE: - { - LOG_DEBUG("Received msg ... PAMSG_EMAIL_CHANGE"); - result.writeShort(APMSG_EMAIL_CHANGE_RESPONSE); - - if (computer.getAccount().get() == NULL) { - result.writeByte(ERRMSG_NO_LOGIN); - break; - } - - std::string email = message.readString(); - if (!stringFilter->isEmailValid(email)) - { - result.writeByte(ERRMSG_INVALID_ARGUMENT); - } - else if (stringFilter->findDoubleQuotes(email)) - { - result.writeByte(ERRMSG_INVALID_ARGUMENT); - } - else if (store.doesEmailAddressExist(email)) - { - result.writeByte(EMAILCHG_EXISTS_EMAIL); - } - else - { - computer.getAccount()->setEmail(email); - result.writeByte(ERRMSG_OK); - } - } + LOG_DEBUG("Received msg ... PAMSG_EMAIL_CHANGE"); + handleEmailChangeMessage(computer, message); break; case PAMSG_EMAIL_GET: - { - LOG_DEBUG("Received msg ... PAMSG_EMAIL_GET"); - result.writeShort(APMSG_EMAIL_GET_RESPONSE); - if (computer.getAccount().get() == NULL) { - result.writeByte(ERRMSG_NO_LOGIN); - break; - } - else - { - result.writeByte(ERRMSG_OK); - result.writeString(computer.getAccount()->getEmail()); - } - } + LOG_DEBUG("Received msg ... PAMSG_EMAIL_GET"); + handleEmailGetMessage(computer); break; case PAMSG_PASSWORD_CHANGE: @@ -186,180 +140,101 @@ AccountHandler::processMessage(NetComputer *comp, MessageIn &message) break; case PAMSG_CHAR_SELECT: - { - LOG_DEBUG("Received msg ... PAMSG_CHAR_SELECT"); - result.writeShort(APMSG_CHAR_SELECT_RESPONSE); - - if (computer.getAccount().get() == NULL) - { - result.writeByte(ERRMSG_NO_LOGIN); - break; // not logged in - } - - unsigned char charNum = message.readByte(); - - Characters &chars = computer.getAccount()->getCharacters(); - // Character ID = 0 to Number of Characters - 1. - if (charNum >= chars.size()) { - // invalid char selection - result.writeByte(ERRMSG_INVALID_ARGUMENT); - break; - } - - std::string address; - short port; - if (!serverHandler->getGameServerFromMap( - chars[charNum]->getMapId(), address, port)) - { - result.writeByte(ERRMSG_FAILURE); - LOG_ERROR("Character Selection: " - << "No game server for the map."); - break; - } - - // set character - computer.setCharacter(chars[charNum]); - CharacterPtr selectedChar = computer.getCharacter(); - result.writeByte(ERRMSG_OK); - - LOG_DEBUG(selectedChar->getName() << - " is trying to enter the servers."); - - std::string magic_token(utils::getMagicToken()); - result.writeString(magic_token, MAGIC_TOKEN_LENGTH); - result.writeString(address); - result.writeShort(port); - // TODO: get correct address and port for the chat server - result.writeString(config.getValue("accountServerAddress", - "localhost")); - result.writeShort(int(config.getValue("accountServerPort", - DEFAULT_SERVER_PORT)) + 2); - - serverHandler->registerGameClient(magic_token, selectedChar); - registerChatClient(magic_token, selectedChar->getName(), - AL_NORMAL); - } + LOG_DEBUG("Received msg ... PAMSG_CHAR_SELECT"); + handleCharacterSelectMessage(computer, message); break; case PAMSG_CHAR_DELETE: - { - LOG_DEBUG("Received msg ... PAMSG_CHAR_DELETE"); - result.writeShort(APMSG_CHAR_DELETE_RESPONSE); - - if (computer.getAccount().get() == NULL) - { - result.writeByte(ERRMSG_NO_LOGIN); - break; // not logged in - } - - unsigned char charNum = message.readByte(); - - Characters &chars = computer.getAccount()->getCharacters(); - // Character ID = 0 to Number of Characters - 1. - if (charNum >= chars.size()) { - // invalid char selection - result.writeByte(ERRMSG_INVALID_ARGUMENT); - break; - } - - // Delete the character - // If the character to delete is the current character, - // get off of it in memory. - if ( computer.getCharacter().get() != NULL ) - { - if ( computer.getCharacter()->getName() == - chars[charNum].get()->getName() ) - { - computer.unsetCharacter(); - } - } - - std::string deletedCharacter = chars[charNum].get()->getName(); - computer.getAccount()->delCharacter(deletedCharacter); - store.flush(computer.getAccount()); - LOG_INFO(deletedCharacter << ": Character deleted..."); - result.writeByte(ERRMSG_OK); - - } + LOG_DEBUG("Received msg ... PAMSG_CHAR_DELETE"); + handleCharacterDeleteMessage(computer, message); break; default: LOG_WARN("AccountHandler::processMessage, Invalid message type " << message.getId()); - result.writeShort(XXMSG_INVALID); + MessageOut result(XXMSG_INVALID); + computer.send(result); break; } - - // return result - if (result.getLength() > 0) - computer.send(result); } void AccountHandler::handleLoginMessage(AccountClient &computer, MessageIn &msg) { - unsigned long clientVersion = msg.readLong(); - std::string username = msg.readString(); - std::string password = msg.readString(); - MessageOut reply(APMSG_LOGIN_RESPONSE); + if (computer.status != CLIENT_LOGIN) + { + reply.writeByte(ERRMSG_FAILURE); + computer.send(reply); + return; + } + + unsigned long clientVersion = msg.readLong(); + if (clientVersion < config.getValue("clientVersion", 0)) { reply.writeByte(LOGIN_INVALID_VERSION); + computer.send(reply); + return; } + + std::string username = msg.readString(); + std::string password = msg.readString(); + if (stringFilter->findDoubleQuotes(username)) { reply.writeByte(ERRMSG_INVALID_ARGUMENT); + computer.send(reply); + return; } - if (computer.getAccount().get() != NULL) { - reply.writeByte(ERRMSG_FAILURE); - } + if (getClientNumber() >= MAX_CLIENTS ) { reply.writeByte(LOGIN_SERVER_FULL); + computer.send(reply); + return; } - else + + // Check if the account exists + Storage &store = Storage::instance("tmw"); + AccountPtr acc = store.getAccount(username); + + if (!acc.get() || acc->getPassword() != password) { - // Check if the account exists - Storage &store = Storage::instance("tmw"); - AccountPtr acc = store.getAccount(username); + reply.writeByte(ERRMSG_INVALID_ARGUMENT); + computer.send(reply); + return; + } - if (!acc.get() || acc->getPassword() != password) - { - reply.writeByte(ERRMSG_INVALID_ARGUMENT); - } - else - { - // Associate account with connection - computer.setAccount(acc); + // Associate account with connection + computer.setAccount(acc); + computer.status = CLIENT_CONNECTED; - reply.writeByte(ERRMSG_OK); - computer.send(reply); + reply.writeByte(ERRMSG_OK); + computer.send(reply); // Acknowledge login - // Return information about available characters - Characters &chars = computer.getAccount()->getCharacters(); - - // Send characters list - for (unsigned int i = 0; i < chars.size(); i++) - { - MessageOut charInfo(APMSG_CHAR_INFO); - charInfo.writeByte(i); // Slot - charInfo.writeString(chars[i]->getName()); - charInfo.writeByte((unsigned char) chars[i]->getGender()); - charInfo.writeByte(chars[i]->getHairStyle()); - charInfo.writeByte(chars[i]->getHairColor()); - charInfo.writeByte(chars[i]->getLevel()); - charInfo.writeShort(chars[i]->getMoney()); - for (int j = 0; j < NB_BASE_ATTRIBUTES; ++j) - charInfo.writeShort(chars[i]->getBaseAttribute(j)); - computer.send(charInfo); - } - return; - } - } + // Return information about available characters + Characters &chars = computer.getAccount()->getCharacters(); - computer.send(reply); + // Send characters list + for (unsigned int i = 0; i < chars.size(); i++) + { + MessageOut charInfo(APMSG_CHAR_INFO); + charInfo.writeByte(i); // Slot + charInfo.writeString(chars[i]->getName()); + charInfo.writeByte((unsigned char) chars[i]->getGender()); + charInfo.writeByte(chars[i]->getHairStyle()); + charInfo.writeByte(chars[i]->getHairColor()); + charInfo.writeByte(chars[i]->getLevel()); + charInfo.writeShort(chars[i]->getMoney()); + + for (int j = 0; j < NB_BASE_ATTRIBUTES; ++j) + charInfo.writeShort(chars[i]->getBaseAttribute(j)); + + computer.send(charInfo); + } + return; } void @@ -367,44 +242,39 @@ AccountHandler::handleLogoutMessage(AccountClient &computer) { MessageOut reply(APMSG_LOGOUT_RESPONSE); - if (computer.getAccount().get() == NULL) + if (computer.status == CLIENT_LOGIN) { reply.writeByte(ERRMSG_NO_LOGIN); } - else + else if (computer.status == CLIENT_CONNECTED) { computer.unsetAccount(); + computer.status = CLIENT_LOGIN; reply.writeByte(ERRMSG_OK); } - + else if (computer.status == CLIENT_QUEQUED) + { + // Delete it from the pendingClient list + mTokenCollector.deletePendingClient(&computer); + // deletePendingClient makes sure that the client get's the message + return; + } computer.send(reply); } -void -AccountHandler::handleReconnectMessage(AccountClient &computer, MessageIn &msg) +void AccountHandler:: +handleReconnectMessage(AccountClient &computer, MessageIn &msg) { - if (computer.getAccount().get() == NULL) + if (computer.status != CLIENT_LOGIN) { - std::string magic_token = msg.readString(MAGIC_TOKEN_LENGTH); - AccountPendingReconnects::iterator i = - pendingReconnects.find(magic_token); - if (i == pendingReconnects.end()) - { - for (AccountPendingClients::iterator j = pendingClients.begin(), - j_end = pendingClients.end(); j != j_end; ++j) - { - if (j->second == &computer) return; // Allready inserted - } - pendingClients.insert(std::make_pair(magic_token, &computer)); - return; //Waiting for the gameserver - } - // Gameserver communication was faster, connect the client - int accountID = i->second; - pendingReconnects.erase(i); - handleReconnectedAccount(computer, accountID); + LOG_DEBUG("Account tried to reconnect, but was allready logged in " + << "or quequed."); return; } - LOG_WARN("Account tried to reconnect, but was allready logged in."); + + std::string magic_token = msg.readString(MAGIC_TOKEN_LENGTH); + computer.status = CLIENT_QUEQUED; // Before the addPendingClient + mTokenCollector.addPendingClient(magic_token, &computer); } void @@ -417,7 +287,11 @@ AccountHandler::handleRegisterMessage(AccountClient &computer, MessageIn &msg) MessageOut reply(APMSG_REGISTER_RESPONSE); - if (clientVersion < config.getValue("clientVersion", 0)) + if (computer.status != CLIENT_LOGIN) + { + reply.writeByte(ERRMSG_FAILURE); + } + else if (clientVersion < config.getValue("clientVersion", 0)) { reply.writeByte(REGISTER_INVALID_VERSION); } @@ -471,6 +345,7 @@ AccountHandler::handleRegisterMessage(AccountClient &computer, MessageIn &msg) // Associate account with connection computer.setAccount(acc); + computer.status = CLIENT_CONNECTED; } } @@ -487,40 +362,101 @@ AccountHandler::handleUnregisterMessage(AccountClient &computer, MessageOut reply(APMSG_UNREGISTER_RESPONSE); + if (computer.status != CLIENT_LOGIN) + { + reply.writeByte(ERRMSG_FAILURE); + computer.send(reply); + return; + } + if (stringFilter->findDoubleQuotes(username)) { reply.writeByte(ERRMSG_INVALID_ARGUMENT); + computer.send(reply); + return; + } + + // See if the account exists + Storage &store = Storage::instance("tmw"); + AccountPtr accPtr = store.getAccount(username); + + if (!accPtr.get() || accPtr->getPassword() != password) + { + reply.writeByte(ERRMSG_INVALID_ARGUMENT); + computer.send(reply); + return; + } + + // Delete account and associated characters + LOG_DEBUG("Unregistered \"" << username + << "\", AccountID: " << accPtr->getID()); + store.delAccount(accPtr); + reply.writeByte(ERRMSG_OK); + + // If the account to delete is the current account we're loggedin + // on, get out of it in memory. + if (computer.getAccount().get() != NULL && + computer.getAccount()->getName() == username) + { + computer.unsetAccount(); + computer.status = CLIENT_LOGIN; + } + computer.send(reply); +} + +void AccountHandler:: +handleEmailChangeMessage(AccountClient &computer, MessageIn &msg) +{ + MessageOut reply(APMSG_EMAIL_CHANGE_RESPONSE); + + if (computer.status != CLIENT_CONNECTED || + computer.getAccount().get() == NULL) + { + reply.writeByte(ERRMSG_NO_LOGIN); + computer.send(reply); + return; + } + + std::string email = msg.readString(); + + Storage &store = Storage::instance("tmw"); + + if (!stringFilter->isEmailValid(email)) + { + reply.writeByte(ERRMSG_INVALID_ARGUMENT); + } + else if (stringFilter->findDoubleQuotes(email)) + { + reply.writeByte(ERRMSG_INVALID_ARGUMENT); + } + else if (store.doesEmailAddressExist(email)) + { + reply.writeByte(EMAILCHG_EXISTS_EMAIL); } else { - // See if the account exists - Storage &store = Storage::instance("tmw"); - AccountPtr accPtr = store.getAccount(username); - LOG_INFO("Unregister for " << username << " with passwords #" - << accPtr->getPassword() << "#" << password << "#"); - if (!accPtr.get() || accPtr->getPassword() != password) - { - reply.writeByte(ERRMSG_INVALID_ARGUMENT); - } - else - { - // Delete account and associated characters - LOG_DEBUG("Farewell " << username << " ..."); - store.delAccount(accPtr); - reply.writeByte(ERRMSG_OK); + computer.getAccount()->setEmail(email); + reply.writeByte(ERRMSG_OK); + } + computer.send(reply); +} - // If the account to delete is the current account we're loggedin - // on, get out of it in memory. - if (computer.getAccount().get() != NULL ) - { - if (computer.getAccount()->getName() == username) - { - computer.unsetAccount(); - } - } - } +void AccountHandler:: +handleEmailGetMessage(AccountClient &computer) +{ + MessageOut reply(APMSG_EMAIL_GET_RESPONSE); + + if (computer.status != CLIENT_CONNECTED || + computer.getAccount().get() == NULL) + { + reply.writeByte(ERRMSG_NO_LOGIN); + computer.send(reply); + return; } + reply.writeByte(ERRMSG_OK); + reply.writeString(computer.getAccount()->getEmail()); + computer.send(reply); } @@ -533,7 +469,8 @@ AccountHandler::handlePasswordChangeMessage(AccountClient &computer, MessageOut reply(APMSG_PASSWORD_CHANGE_RESPONSE); - if (computer.getAccount().get() == NULL) + if (computer.status != CLIENT_CONNECTED || + computer.getAccount().get() == NULL) { reply.writeByte(ERRMSG_NO_LOGIN); } @@ -570,7 +507,9 @@ AccountHandler::handleCharacterCreateMessage(AccountClient &computer, MessageOut reply(APMSG_CHAR_CREATE_RESPONSE); - if (computer.getAccount().get() == NULL) { + if (computer.status != CLIENT_CONNECTED || + computer.getAccount().get() == NULL) + { reply.writeByte(ERRMSG_NO_LOGIN); } else if (!stringFilter->filterContent(name)) @@ -703,24 +642,111 @@ AccountHandler::handleCharacterCreateMessage(AccountClient &computer, computer.send(reply); } -void -registerAccountReconnect(int accountID, const std::string& magic_token) +void AccountHandler:: +handleCharacterSelectMessage(AccountClient &computer, MessageIn &msg) { - AccountPendingClients::iterator i = pendingClients.find(magic_token); - if (i == pendingClients.end()) + MessageOut reply(APMSG_CHAR_SELECT_RESPONSE); + + if (computer.status != CLIENT_CONNECTED || + computer.getAccount().get() == NULL) { - pendingReconnects.insert(std::make_pair(magic_token, accountID)); + reply.writeByte(ERRMSG_NO_LOGIN); + computer.send(reply); + return; // not logged in } - else + + unsigned char charNum = msg.readByte(); + Characters &chars = computer.getAccount()->getCharacters(); + + // Character ID = 0 to Number of Characters - 1. + if (charNum >= chars.size()) + { + // invalid char selection + reply.writeByte(ERRMSG_INVALID_ARGUMENT); + computer.send(reply); + return; + } + + std::string address; + short port; + if (!serverHandler->getGameServerFromMap( + chars[charNum]->getMapId(), address, port)) + { + LOG_ERROR("Character Selection: No game server for the map."); + reply.writeByte(ERRMSG_FAILURE); + computer.send(reply); + return; + } + + // set character + computer.setCharacter(chars[charNum]); + CharacterPtr selectedChar = computer.getCharacter(); + reply.writeByte(ERRMSG_OK); + + LOG_DEBUG(selectedChar->getName() << " is trying to enter the servers."); + + std::string magic_token(utils::getMagicToken()); + reply.writeString(magic_token, MAGIC_TOKEN_LENGTH); + reply.writeString(address); + reply.writeShort(port); + + // TODO: get correct address and port for the chat server + reply.writeString(config.getValue("accountServerAddress", "localhost")); + reply.writeShort(int(config.getValue("accountServerPort", + DEFAULT_SERVER_PORT)) + 2); + + serverHandler->registerGameClient(magic_token, selectedChar); + registerChatClient(magic_token, selectedChar->getName(), AL_NORMAL); + + computer.send(reply); +} + +void AccountHandler:: +handleCharacterDeleteMessage(AccountClient &computer, MessageIn &msg) +{ + MessageOut reply(APMSG_CHAR_DELETE_RESPONSE); + + if (computer.status != CLIENT_CONNECTED || + computer.getAccount().get() == NULL) + { + reply.writeByte(ERRMSG_NO_LOGIN); + computer.send(reply); + return; // not logged in + } + + unsigned char charNum = msg.readByte(); + Characters &chars = computer.getAccount()->getCharacters(); + + // Character ID = 0 to Number of Characters - 1. + if (charNum >= chars.size()) { - AccountClient &computer = *(i->second); - handleReconnectedAccount(computer, accountID); - pendingClients.erase(i); + // invalid char selection + reply.writeByte(ERRMSG_INVALID_ARGUMENT); + computer.send(reply); + return; // not logged in } + + // Delete the character. If the character to delete is the current + // character, get off of it in memory. + if (computer.getCharacter().get() != NULL && + computer.getCharacter()->getName() == chars[charNum].get()->getName()) + computer.unsetCharacter(); + + std::string deletedCharacter = chars[charNum].get()->getName(); + computer.getAccount()->delCharacter(deletedCharacter); + + Storage &store = Storage::instance("tmw"); + store.flush(computer.getAccount()); + + LOG_INFO(deletedCharacter << ": Character deleted..."); + + reply.writeByte(ERRMSG_OK); + + computer.send(reply); } void -handleReconnectedAccount(AccountClient &computer, int accountID) +AccountHandler::tokenMatched(AccountClient *computer, int accountID) { MessageOut reply(APMSG_RECONNECT_RESPONSE); @@ -729,13 +755,14 @@ handleReconnectedAccount(AccountClient &computer, int accountID) AccountPtr acc = store.getAccountByID(accountID); // Associate account with connection - computer.setAccount(acc); + computer->setAccount(acc); + computer->status = CLIENT_CONNECTED; reply.writeByte(ERRMSG_OK); - computer.send(reply); + computer->send(reply); // Return information about available characters - Characters &chars = computer.getAccount()->getCharacters(); + Characters &chars = computer->getAccount()->getCharacters(); // Send characters list for (unsigned int i = 0; i < chars.size(); i++) @@ -753,6 +780,25 @@ handleReconnectedAccount(AccountClient &computer, int accountID) { charInfo.writeShort(chars[i]->getBaseAttribute(j)); } - computer.send(charInfo); + computer->send(charInfo); } } + +void +AccountHandler::deletePendingClient(AccountClient* computer) +{ + // Something might have changed since it was inserted + if (computer->status != CLIENT_QUEQUED) return; + + MessageOut msg(APMSG_CONNECTION_TIMEDOUT); + computer->disconnect(msg); + // The computer will be deleted when the disconnect event is processed +} + +void +AccountHandler::deletePendingConnect(int accountID) +{ + // NOOP + // No memory was allocated for the PendingConnect (that was not + // allocated to a countedPtr). +} diff --git a/src/account-server/accounthandler.hpp b/src/account-server/accounthandler.hpp index da9140e4..1c0de2e4 100644 --- a/src/account-server/accounthandler.hpp +++ b/src/account-server/accounthandler.hpp @@ -25,6 +25,7 @@ #define _TMWSERV_ACCOUNTHANDLER_H_ #include "net/connectionhandler.hpp" +#include "utils/tokencollector.hpp" class AccountClient; @@ -40,11 +41,44 @@ class AccountHandler : public ConnectionHandler { public: /** + * Constructor + */ + AccountHandler(); + + /** * Start the handler */ bool startListen(enet_uint16 port); + /** + * Combines a client with it's account. + * (Needed for TokenCollector) + */ + void + tokenMatched(AccountClient *computer, int accountID); + + /** + * Deletes a pending client's data. + * (Needed for TokenCollector) + */ + void + deletePendingClient(AccountClient* computer); + + /** + * Deletes a pending connection's data. + * (Needed for TokenCollector) + */ + void + deletePendingConnect(int accountID); + + /** + * TokenCollector, used to login a client without the client having to + * send username and password a second time. + */ + TokenCollector<AccountHandler, AccountClient*, int> + mTokenCollector; + protected: /** * Process account related messages. @@ -76,10 +110,24 @@ class AccountHandler : public ConnectionHandler handleUnregisterMessage(AccountClient &computer, MessageIn &msg); void + handleEmailChangeMessage(AccountClient &computer, MessageIn &msg); + + void + handleEmailGetMessage(AccountClient &computer); + + void handlePasswordChangeMessage(AccountClient &computer, MessageIn &msg); void handleCharacterCreateMessage(AccountClient &computer, MessageIn &msg); + + void + handleCharacterSelectMessage(AccountClient &computer, MessageIn &msg); + + void + handleCharacterDeleteMessage(AccountClient &computer, MessageIn &msg); }; +extern AccountHandler * accountHandler; + #endif diff --git a/src/account-server/serverhandler.cpp b/src/account-server/serverhandler.cpp index 53cc53f5..b1fba564 100644 --- a/src/account-server/serverhandler.cpp +++ b/src/account-server/serverhandler.cpp @@ -32,9 +32,7 @@ #include "net/netcomputer.hpp" #include "utils/logger.h" #include "utils/tokendispenser.hpp" - -extern void registerAccountReconnect(int accountID, - const std::string &magic_token); +#include "utils/tokencollector.hpp" bool ServerHandler::startListen(enet_uint16 port) { @@ -173,7 +171,8 @@ void ServerHandler::processMessage(NetComputer *comp, MessageIn &msg) CharacterPtr ptr = store.getCharacter(characterID); int accountID = ptr->getAccountID(); - registerAccountReconnect(accountID, magic_token); + accountHandler-> + mTokenCollector.addPendingConnect(magic_token, accountID); } break; diff --git a/src/account-server/serverhandler.hpp b/src/account-server/serverhandler.hpp index cc269165..9de15a89 100644 --- a/src/account-server/serverhandler.hpp +++ b/src/account-server/serverhandler.hpp @@ -25,7 +25,7 @@ #define _TMWSERV_SERVERHANDLER_H_ #include <map> - +#include "account-server/accounthandler.hpp" #include "account-server/characterdata.hpp" #include "net/connectionhandler.hpp" #include "utils/countedptr.h" diff --git a/src/defines.h b/src/defines.h index c1ad4fb0..9394d3c9 100644 --- a/src/defines.h +++ b/src/defines.h @@ -142,6 +142,9 @@ enum { PAMSG_RECONNECT = 0x0065, // B*32 token APMSG_RECONNECT_RESPONSE = 0x0066, // B error + APMSG_CONNECTION_TIMEDOUT = 0x0070, // - + GPMSG_CONNECTION_TIMEDOUT = 0x0071, // - + // Game GPMSG_PLAYER_MAP_CHANGE = 0x0100, // S filename, W x, W y GPMSG_PLAYER_SERVER_CHANGE = 0x0101, // B*32 token, S game address, W game port diff --git a/src/game-server/accountconnection.cpp b/src/game-server/accountconnection.cpp index dea2e82c..e54605ff 100644 --- a/src/game-server/accountconnection.cpp +++ b/src/game-server/accountconnection.cpp @@ -31,8 +31,7 @@ #include "net/messageout.hpp" #include "utils/logger.h" #include "utils/tokendispenser.hpp" - -extern void registerGameClient(std::string const &, Character *); +#include "utils/tokencollector.hpp" bool AccountConnection::start() { @@ -70,7 +69,7 @@ void AccountConnection::processMessage(MessageIn &msg) std::string token = msg.readString(MAGIC_TOKEN_LENGTH); Character *ptr = new Character(msg); ptr->setSpeed(150); // TODO - registerGameClient(token, ptr); + gameHandler->mTokenCollector.addPendingConnect(token, ptr); } break; case AGMSG_ACTIVE_MAP: diff --git a/src/game-server/gamehandler.cpp b/src/game-server/gamehandler.cpp index 7b82c1a8..b4f9340c 100644 --- a/src/game-server/gamehandler.cpp +++ b/src/game-server/gamehandler.cpp @@ -39,78 +39,9 @@ #include "utils/logger.h" #include "utils/tokendispenser.hpp" -enum +GameHandler::GameHandler(): + mTokenCollector(this) { - CLIENT_LOGIN = 0, - CLIENT_CONNECTED, - CLIENT_CHANGE_SERVER -}; - -struct GameClient: NetComputer -{ - GameClient(ENetPeer *peer) - : NetComputer(peer), character(NULL), status(CLIENT_LOGIN) {} - Character *character; - int status; -}; - -struct GamePendingLogin -{ - Character *character; - int timeout; -}; - -typedef std::map< std::string, GamePendingLogin > GamePendingLogins; -typedef std::map< std::string, GameClient * > GamePendingClients; - -/** - * The pending logins represent clients who were given a magic token by the - * account server but who have not yet logged in to the game server. - */ -static GamePendingLogins pendingLogins; - -/** - * The pending clients represent clients who tried to login to the game server, - * but for which no magic token is available yet. This can happen when the - * communication between the account server and client went faster than the - * communication between the account server and the game server. - */ -static GamePendingClients pendingClients; - -/** - * Links a client to a character. - */ -static void linkCharacter(GameClient *computer, Character *ch) -{ - computer->character = ch; - computer->status = CLIENT_CONNECTED; - ch->setClient(computer); - MessageOut result(GPMSG_CONNECT_RESPONSE); - result.writeByte(ERRMSG_OK); - computer->send(result); - gameState->insert(ch); - Inventory(ch).sendFull(); -} - -/** - * Notification that a particular token has been given to allow a certain - * character to enter the game. - */ -void registerGameClient(std::string const &token, Character *ch) -{ - GamePendingClients::iterator i = pendingClients.find(token); - if (i != pendingClients.end()) - { - linkCharacter(i->second, ch); - pendingClients.erase(i); - } - else - { - GamePendingLogin p; - p.character = ch; - p.timeout = 300; // world ticks - pendingLogins.insert(std::make_pair(token, p)); - } } bool GameHandler::startListen(enet_uint16 port) @@ -124,23 +55,20 @@ NetComputer *GameHandler::computerConnected(ENetPeer *peer) return new GameClient(peer); } -void GameHandler::computerDisconnected(NetComputer *computer) +void GameHandler::computerDisconnected(NetComputer *comp) { - for (GamePendingClients::iterator i = pendingClients.begin(), - i_end = pendingClients.end(); i != i_end; ++i) + GameClient &computer = *static_cast< GameClient * >(comp); + + if (computer.status == CLIENT_QUEQUED) { - if (i->second == computer) - { - pendingClients.erase(i); - break; - } + mTokenCollector.deletePendingClient(&computer); } - if (Character *ch = static_cast< GameClient * >(computer)->character) + else if (Character *ch = computer.character) { gameState->remove(ch); delete ch; } - delete computer; + delete &computer; } void GameHandler::kill(Character *ch) @@ -184,21 +112,6 @@ void GameHandler::completeServerChange(int id, std::string const &token, void GameHandler::process() { ConnectionHandler::process(); - - // Removes characters that have been left unconnected for too long. - GamePendingLogins::iterator i = pendingLogins.begin(); - while (i != pendingLogins.end()) - { - if (--i->second.timeout <= 0) - { - delete i->second.character; - pendingLogins.erase(i++); - } - else - { - ++i; - } - } } void GameHandler::processMessage(NetComputer *comp, MessageIn &message) @@ -209,20 +122,10 @@ void GameHandler::processMessage(NetComputer *comp, MessageIn &message) if (computer.status == CLIENT_LOGIN) { if (message.getId() != PGMSG_CONNECT) return; + std::string magic_token = message.readString(MAGIC_TOKEN_LENGTH); - GamePendingLogins::iterator i = pendingLogins.find(magic_token); - if (i == pendingLogins.end()) - { - for (GamePendingClients::iterator j = pendingClients.begin(), - j_end = pendingClients.end(); j != j_end; ++j) - { - if (j->second == &computer) return; - } - pendingClients.insert(std::make_pair(magic_token, &computer)); - return; - } - linkCharacter(&computer, i->second.character); - pendingLogins.erase(i); + mTokenCollector.addPendingClient(magic_token, &computer); + computer.status == CLIENT_QUEQUED; return; } else if (computer.status != CLIENT_CONNECTED) @@ -371,3 +274,38 @@ void GameHandler::sendTo(Character *beingPtr, MessageOut &msg) assert(client && client->status == CLIENT_CONNECTED); client->send(msg); } + +void +GameHandler::tokenMatched(GameClient* computer, Character* character) +{ + computer->character = character; + computer->status = CLIENT_CONNECTED; + + character->setClient(computer); + + MessageOut result(GPMSG_CONNECT_RESPONSE); + result.writeByte(ERRMSG_OK); + computer->send(result); + + gameState->insert(character); + + Inventory(character).sendFull(); +} + +void +GameHandler::deletePendingClient(GameClient* computer) +{ + // Something might have changed since it was inserted + if (computer->status != CLIENT_QUEQUED) return; + + MessageOut msg(GPMSG_CONNECTION_TIMEDOUT); + + // The computer will be deleted when the disconnect event is processed + computer->disconnect(msg); +} + +void +GameHandler::deletePendingConnect(Character* character) +{ + delete character; +} diff --git a/src/game-server/gamehandler.hpp b/src/game-server/gamehandler.hpp index 9f76e865..312043f2 100644 --- a/src/game-server/gamehandler.hpp +++ b/src/game-server/gamehandler.hpp @@ -26,6 +26,24 @@ #include "game-server/character.hpp" #include "net/connectionhandler.hpp" +#include "net/netcomputer.hpp" +#include "utils/tokencollector.hpp" + +enum +{ + CLIENT_LOGIN = 0, + CLIENT_CONNECTED, + CLIENT_CHANGE_SERVER, + CLIENT_QUEQUED +}; + +struct GameClient: NetComputer +{ + GameClient(ENetPeer *peer) + : NetComputer(peer), character(NULL), status(CLIENT_LOGIN) {} + Character *character; + int status; +}; /** * Manages connections to game client. @@ -34,6 +52,10 @@ class GameHandler: public ConnectionHandler { public: /** + * Constructor + */ + GameHandler(); + /** * Processes messages and cleans outdated characters. */ void process(); @@ -64,6 +86,34 @@ class GameHandler: public ConnectionHandler void completeServerChange(int id, std::string const &token, std::string const &address, int port); + /** + * Combines a client with it's character. + * (Needed for TokenCollector) + */ + void + tokenMatched(GameClient* computer, Character* character); + + /** + * Deletes a pending client's data. + * (Needed for TokenCollector) + */ + void + deletePendingClient(GameClient* computer); + + /** + * Deletes a pending connection's data. + * (Needed for TokenCollector) + */ + void + deletePendingConnect(Character* character); + + /** + * TokenCollector, used to match a gameclient with the data received + * from the accountserver. + */ + TokenCollector<GameHandler, GameClient*, Character*> + mTokenCollector; + protected: NetComputer *computerConnected(ENetPeer *); void computerDisconnected(NetComputer *); diff --git a/src/net/netcomputer.cpp b/src/net/netcomputer.cpp index f0b677ec..d35779da 100644 --- a/src/net/netcomputer.cpp +++ b/src/net/netcomputer.cpp @@ -92,11 +92,10 @@ operator <<(std::ostream &os, const NetComputer &comp) << ((comp.mPeer->address.host & 0xff000000) >> 24); else // big-endian - // TODO: test this - os << ((comp.mPeer->address.host & 0x000000ff) << 24) << "." - << ((comp.mPeer->address.host & 0x0000ff00) << 16) << "." - << ((comp.mPeer->address.host & 0x00ff0000) << 8) << "." - << ((comp.mPeer->address.host & 0xff000000) << 0); + os << ((comp.mPeer->address.host & 0xff000000) >> 24) << "." + << ((comp.mPeer->address.host & 0x00ff0000) >> 16) << "." + << ((comp.mPeer->address.host & 0x0000ff00) >> 8) << "." + << ((comp.mPeer->address.host & 0x000000ff) >> 0); return os; } diff --git a/src/utils/tokencollector.hpp b/src/utils/tokencollector.hpp new file mode 100644 index 00000000..036ffe36 --- /dev/null +++ b/src/utils/tokencollector.hpp @@ -0,0 +1,326 @@ +/* + * The Mana World Server + * Copyright 2007 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or any later version. + * + * The Mana World is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with The Mana World; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + +#ifndef _TMW_TOKENCOLLECTOR_HPP +#define _TMW_TOKENCOLLECTOR_HPP + +#include <string> +#include <list> +#include <time.h> + +#include "utils/logger.h" + +#define NB_ACTIONS_BETWEEN_OUTDATED_CHECKS 100 + +/** + * Because the timeStamp is not updated for every new list item and the + * removeOutdated function is not called on regular intervals, a list item + * might persist significantly longer then this. + */ +#define TIMEOUT 10 // in seconds + + +/** + * A structure used for the list items, + * prefixed with 'TC_' to avoid any conflicts. + */ +template <typename T> +struct TC_ListItem +{ + /** The magic_token aka passToken */ + std::string token; + + /** The actual data, which will be sent back when matched */ + T payload; + + /** + * The amount of seconds since TokenCollector was running, + * at the last outdated check. + */ + time_t timeStamp; +}; + +// Prototype + +/** + * \brief A class for storing and matching magic_tokens + * + * T is used for the class of the owner, U is the client payload, + * V is the connect payload + * + * The owning class must implement as it's member functions; + * - destroyPendingClient(U clientPayload), + * - destroyPendingConnect(V connectPayload) and + * - tokenMatched(U clientPayload, V connectPayload), + * in a way that TokenCollector can reach them + * (public, or declare TokenCollector a friend class). + */ +template <class T, class U, class V> +class TokenCollector +{ + public: + /** + * Constructor. + */ + TokenCollector(T * owner); + + /** + * Destructor. + */ + ~TokenCollector(); + + /** + * \brief Adds a pending client + * + * Searches the pending connects for a match. + * Calls mOwner->tokenMatched when a match is found. + * Inserts in mPendingClients if no match is found. + */ + void addPendingClient(const std::string & token, U clientPayload); + + /** + * \brief Removes a pending client from the list + * + * Searches the pending client for the payload. + */ + void deletePendingClient(U clientPayload); + + /** + * \brief Adds a pending connect + * + * Searches the pending clients for a match. + * Calls mOwner->tokenMatched when a match is found. + * Inserts in mPendingConnects if no match is found. + */ + void addPendingConnect(const std::string & token, V connectPayload); + + private: + + /** + * \brief A simple list of all pending clients + * + * On a well setup system, this list is mostly empty. + * The servers send the magic_token to the other server, before they + * send it to the client. In a well setup system, the route + * server1 -> server2 is faster then server1 -> client -> server2. + * + * A std::list is used because the basic work cycle for one client + * (search PendingClients, insert PendingConnects, search + * PendingConnects, remove pendingConnect) will run in (allmost) + * constant time, when the size of PendingClients is small. + * This can not be said for a map or sorted vector. + * + * The list items are pointers to void, because a list can not be + * instantiationised with an incomplete type. + */ + std::list< void* > mPendingClients; + + /** + * \brief A simple list of all pending clients. + * + * See mPendingClients for details. + */ + std::list< void* > mPendingConnects; + + /** + * \brief The number of actions since the last time that the lists + * where checked for outdated pendingClients or + * pendingConnects. + */ + int mNumberOfActions; + + /** + * \brief The number of seconds between the creation of TokenCollector + * and the last removeOutdated. + */ + time_t mTimeNow; + + /** + * \brief The time that TokenCollector was created, used for keeping + * the variable times low numbers. + */ + time_t mTimeStart; + + /** + * \brief Pointer to the owner of this TokenCollector object. + */ + T * mOwner; + + /** + * \brief Removes outdated entries. + */ + void removeOutdated(); +}; + +// Implementation + +template <class T, class U, class V> +TokenCollector<T, U, V>:: +TokenCollector(T * owner): + mNumberOfActions(0), mTimeStart(time(NULL)), mTimeNow(0), mOwner(owner) +{ +} + +template <class T, class U, class V> +TokenCollector<T, U, V>:: +~TokenCollector() +{ + if (mPendingClients.size()) + LOG_INFO("TokenCollector deleted with " << + mPendingClients.size() << + " clients still pending."); + + if (mPendingConnects.size()) + LOG_INFO("TokenCollector deleted with " << + mPendingConnects.size() << + " connects still pending."); +} + +template <class T, class U, class V> +void TokenCollector<T, U, V>:: +addPendingClient(const std::string & token, U clientPayload) +{ + // Find could also be used for a list, but because new items are + // inserted at the top, we want to start looking there. + for (std::list< void* >::iterator it = mPendingConnects.begin(), + it_end = mPendingConnects.end(); it != it_end; it++) + { + // Because the pointer to the listItem was stored as a pointer to + // void, we have to cast it back to a pointer to a listItem. + // Examples: it = void**; *it = void*; + // ((TC_ListItem< V >*)*it) = TC_ListItem< V >*; + // --------------------------------------------- + if (((TC_ListItem< V >*)*it)->token == token) // Found a match + { + mOwner->tokenMatched(clientPayload, + ((TC_ListItem< V >*)*it)->payload); + delete ((TC_ListItem< V >*)*it); + mPendingConnects.erase(it); + return; // Done + } + } + + TC_ListItem< U >* listItem = new TC_ListItem< U >; + listItem->token = token; + listItem->payload = clientPayload; + listItem->timeStamp = mTimeNow; + + mPendingClients.push_front((void*)listItem); + + if (!(++mNumberOfActions < NB_ACTIONS_BETWEEN_OUTDATED_CHECKS)) + removeOutdated(); +} + +template <class T, class U, class V> +void TokenCollector<T, U, V>:: +deletePendingClient(U clientPayload) +{ + // Find could also be used for a list, but because new items are + // inserted at the top, we want to start looking there. + for (std::list< void* >::iterator it = mPendingClients.begin(), + it_end = mPendingClients.end(); it != it_end; it++) + { + // Because the pointer to the listItem was stored as a pointer to + // void, we have to cast it back to a pointer to a listItem. + // Examples: it = void**; *it = void*; + // ((TC_ListItem< U >*)*it) = TC_ListItem< U >*; + // --------------------------------------------- + if (((TC_ListItem< U >*)*it)->payload == clientPayload) // Found a match + { + delete ((TC_ListItem< U >*)*it); + mPendingConnects.erase(it); + return; // Done + } + } +} + +template <class T, class U, class V> +void TokenCollector<T, U, V>:: +addPendingConnect(const std::string & token, V connectPayload) +{ + // Find could also be used for a list, but because new items are + // inserted at the top, we want to start looking there. + for (std::list< void* >::iterator it = mPendingClients.begin(), + it_end = mPendingClients.end(); it != it_end; it++) + { + // Because the pointer to the listItem was stored as a pointer to + // void, we have to cast it back to a pointer to a listItem. + // Examples: it = void**; *it = void*; + // ((TC_ListItem< U >*)*it) = TC_ListItem< U >*; + // --------------------------------------------- + if (((TC_ListItem< U >*)*it)->token == token) // Found a match + { + mOwner->tokenMatched(((TC_ListItem< U >*)*it)->payload, + connectPayload); + delete ((TC_ListItem< U >*)*it); + mPendingClients.erase(it); + return; // Done + } + } + + TC_ListItem< V >* listItem = new TC_ListItem< V >; + listItem->token = token; + listItem->payload = connectPayload; + listItem->timeStamp = mTimeNow; + + mPendingConnects.push_front((void*)listItem); + + if (!(++mNumberOfActions < NB_ACTIONS_BETWEEN_OUTDATED_CHECKS)) + removeOutdated(); +} + +template <class T, class U, class V> +void TokenCollector<T, U, V>:: +removeOutdated() +{ + time_t eraseTime = (mTimeNow > (time_t)TIMEOUT) ? + (mTimeNow - (time_t)TIMEOUT) : (time_t) 0; + + // See addPendingClient for a comment about the casting. + while ((mPendingClients.size()) && + (((TC_ListItem< U >*)(mPendingClients.back()))->timeStamp < eraseTime)) + { + mOwner->deletePendingClient( + ((TC_ListItem< U >*)(mPendingClients.back()))->payload); + delete ((TC_ListItem< U >*)(mPendingClients.back())); + mPendingClients.pop_back(); + } + + while ((mPendingConnects.size()) && + (((TC_ListItem< V >*)(mPendingConnects.back()))->timeStamp < eraseTime)) + { + mOwner->deletePendingConnect( + ((TC_ListItem< V >*)(mPendingConnects.back()))->payload); + delete ((TC_ListItem< U >*)(mPendingConnects.back())); + mPendingConnects.pop_back(); + } + + /** + * Change the timeStap after the check, else everything that was just + * inserted might be thrown away. + */ + mTimeNow = time(NULL) - mTimeStart; + + mNumberOfActions = 0; // Reset the counter. +} + +#endif // _TMW_TOKENCOLLECTOR_HPP |