// ConnectionServer.cpp
// copyright 2001 Verant Interactive

//-----------------------------------------------------------------------

#include "FirstConnectionServer.h"
#include "ConnectionServer.h"

#include "Archive/ByteStream.h"
#include "CentralConnection.h"
#include "ChatServerConnection.h"
#include "ClientConnection.h"
#include "ConfigConnectionServer.h"
#include "ConnectionServerMetricsData.h"
#include "CustomerServiceConnection.h"
#include "GameConnection.h"
#include "PseudoClientConnection.h"
#include "SessionApiClient.h"
#include "UdpLibrary.h"
#include "UnicodeUtils.h"
#include "serverKeyShare/KeyShare.h"
#include "serverMetrics/MetricsManager.h"
#include "serverNetworkMessages/CentralConnectionServerMessages.h"
#include "serverNetworkMessages/CharacterListMessage.h"
#include "serverNetworkMessages/ExcommunicateGameServerMessage.h"
#include "serverNetworkMessages/GameConnectionServerMessages.h"
#include "serverNetworkMessages/GameServerForLoginMessage.h"
#include "serverNetworkMessages/LoginKeyPush.h"
#include "serverNetworkMessages/NewClient.h"
#include "serverNetworkMessages/ProfilerOperationMessage.h"
#include "serverNetworkMessages/RandomName.h"
#include "serverNetworkMessages/SetConnectionServerPublic.h"
#include "serverNetworkMessages/UpdateConnectionServerStatus.h"
#include "serverNetworkMessages/UpdatePlayerCountMessage.h"
#include "serverNetworkMessages/ValidateAccountReplyMessage.h"
#include "serverNetworkMessages/ValidateCharacterForLoginReplyMessage.h"
#include "serverNetworkMessages/VerifyAndLockName.h"
#include "serverUtility/AdminAccountManager.h"
#include "sharedDebug/Profiler.h"
#include "sharedFoundation/Clock.h"
#include "sharedFoundation/ConfigFile.h"
#include "sharedFoundation/Os.h"
#include "sharedFoundation/Timer.h"
#include "sharedGame/PlatformFeatureBits.h"
#include "sharedLog/Log.h"
#include "sharedLog/LogManager.h"
#include "sharedLog/SetupSharedLog.h"

#include "sharedNetwork/NetworkSetupData.h"
#include "sharedNetwork/Service.h"
#include "sharedNetwork/UdpSock.h"
#include "sharedNetworkMessages/ClientCentralMessages.h"
#include "sharedNetworkMessages/GenericValueTypeMessage.h"
#include "sharedUtility/DataTableManager.h"
#include <vector>

#include "sharedFoundation/CrcConstexpr.hpp"

// ======================================================================
namespace ConnectionServerNamespace {
    ConnectionServer *s_connectionServer = 0;
    NetworkSetupData *s_clientServiceSetup = 0;

    const std::string SCENE_NAME_TUTORIAL = "tutorial";
    const std::string SCENE_NAME_FALCON_PREFIX = "space_npe_falcon";
};

using namespace ConnectionServerNamespace;

// ======================================================================

ConnectionServer &ConnectionServer::instance() {
    return *s_connectionServer;
}

//-----------------------------------------------------------------------

ConnectionServer::ConnectionServer()
        : MessageDispatch::Receiver(), chatService(0), customerService(0), clientServicePrivate(0),
          clientServicePublic(0), gameService(0), loginServerKeys(0), done(false), m_id(0), m_metricsData(0),
          centralConnection(0), chatServers(), customerServiceServers(), clientMap(), connectedMap(), gameServerMap(),
          freeTrials(), networkBarrier(0), pingSocket(new UdpSock), m_recoverTime(0), m_pingTrafficNumBytes(0),
          m_recoveringClientList() {
    if (s_clientServiceSetup == 0) {
        s_clientServiceSetup = new NetworkSetupData;
    }

    s_clientServiceSetup->maxOutstandingPackets = ConfigConnectionServer::getClientMaxOutstandingPackets();
    s_clientServiceSetup->maxRawPacketSize = ConfigConnectionServer::getClientMaxRawPacketSize();
    s_clientServiceSetup->maxConnections = ConfigConnectionServer::getClientMaxConnections();
    s_clientServiceSetup->fragmentSize = ConfigConnectionServer::getClientFragmentSize();
    s_clientServiceSetup->maxDataHoldTime = ConfigConnectionServer::getClientMaxDataHoldTime();
    s_clientServiceSetup->hashTableSize = ConfigConnectionServer::getClientHashTableSize();
    s_clientServiceSetup->port = ConfigConnectionServer::getClientServicePortPublic();
    s_clientServiceSetup->compress = ConfigConnectionServer::getCompressClientNetworkTraffic();
    s_clientServiceSetup->useTcp = false;

    loginServerKeys = new KeyServer(20);

    Address a("", ConfigConnectionServer::getPingPort());
    IGNORE_RETURN(pingSocket->bind(a));
}

//-----------------------------------------------------------------------

ConnectionServer::~ConnectionServer() {
    delete pingSocket;
    pingSocket = 0;

    delete loginServerKeys;
    loginServerKeys = 0;

    centralConnection = 0;

    chatServers.clear();

    customerServiceServers.clear();

    connectedMap.clear();

    clientServicePublic = 0;
    clientServicePrivate = 0;
    chatService = 0;
    customerService = 0;
    gameService = 0;
    gameServerMap.clear();
    freeTrials.clear();
    delete s_clientServiceSetup;
}

//-----------------------------------------------------------------------

const CustomerServiceConnection *ConnectionServer::getCustomerServiceConnection() {
    if (!instance().customerServiceServers.empty()) {
        return (*(instance().customerServiceServers.begin()));
    }
    return nullptr;
}

//-----------------------------------------------------------------------

//TODO: This assumes that all characters that aren't on a game server
// are waiting for this gameserver.  This assumption is bad:  they could
// be waiting on a different gameserver, or they could be in the process
// of being validated

void ConnectionServer::addGameConnection(unsigned long gameServerId, GameConnection *gc) {
    static ConnectionServer &cs = instance();

    cs.gameServerMap[gameServerId] = gc;//@todo check for dupe
    // find characters pending for THIS gameserver
    SuidMap::iterator i;
    for (i = cs.connectedMap.begin(); i != cs.connectedMap.end(); ++i) {
        ClientConnection *c = (*i).second;
        if (!c->getHasBeenSentToGameServer()) {
            IGNORE_RETURN(c->sendToGameServer());
        }
    }
}

//-----------------------------------------------------------------------

bool
ConnectionServer::decryptToken(const KeyShare::Token &token, uint32 &stationUserId, bool &secure, std::string &accountName) {
    static ConnectionServer &cs = instance();

    //Also the sizeof(int) is likewise magic from the session api
    uint32 len = sizeof(uint32) + sizeof(bool) + MAX_ACCOUNT_NAME_LENGTH + 1;
    unsigned char *keyBuffer = new unsigned char[len];
    unsigned char *keyBufferPointer = keyBuffer;
    memset(keyBuffer, 0, len);

    bool retval = cs.loginServerKeys->decipherToken(token, keyBuffer, len);

    if (!retval) {
        return retval;
    }

    char *tmpBuffer = new char[MAX_ACCOUNT_NAME_LENGTH + 1];
    memset(tmpBuffer, 0, MAX_ACCOUNT_NAME_LENGTH + 1);

    memcpy(&stationUserId, keyBufferPointer, sizeof(uint32));
    keyBufferPointer += sizeof(uint32);
    memcpy(&secure, keyBufferPointer, sizeof(bool));
    keyBufferPointer += sizeof(bool);
    memcpy(tmpBuffer, keyBufferPointer, MAX_ACCOUNT_NAME_LENGTH);
    accountName = tmpBuffer;
    delete[] tmpBuffer;
    delete[] keyBuffer;
    return retval;
}

bool ConnectionServer::decryptToken(const KeyShare::Token &token, char *sessionKey, StationId &stationId) {
    static ConnectionServer &cs = instance();

    uint32 len = apiSessionIdWidth + sizeof(StationId);
    unsigned char *keyBuffer = new unsigned char[len + 1];
    unsigned char *keyBufferPointer = keyBuffer;
    memset(keyBuffer, 0, sizeof(*keyBuffer));

    bool retval = cs.loginServerKeys->decipherToken(token, keyBuffer, len);

    if (!retval) {
        return retval;
    }

    memcpy(sessionKey, keyBufferPointer, len);
    keyBufferPointer += apiSessionIdWidth;
    memcpy(&stationId, keyBufferPointer, sizeof(StationId));
    delete[] keyBuffer;
    return retval;
}

//-----------------------------------------------------------------------

const Service *ConnectionServer::getChatService() {
    static ConnectionServer &cs = instance();
    return cs.chatService;
}

//-----------------------------------------------------------------------

const Service *ConnectionServer::getCustomerService() {
    static ConnectionServer &cs = instance();
    return cs.customerService;
}

//-----------------------------------------------------------------------

Service *ConnectionServer::getClientServicePrivate() {
    static ConnectionServer &cs = instance();
    return cs.clientServicePrivate;
}

//-----------------------------------------------------------------------

Service *ConnectionServer::getClientServicePublic() {
    static ConnectionServer &cs = instance();
    return cs.clientServicePublic;
}

//-----------------------------------------------------------------------

KeyShare::Token ConnectionServer::makeToken(const unsigned char *newData, const uint32 dataLen) {
    static ConnectionServer &cs = instance();
    return cs.loginServerKeys->makeToken(newData, dataLen);
}

//-----------------------------------------------------------------------

void ConnectionServer::pushKey(const KeyShare::Key &newKey) {
    static ConnectionServer &cs = instance();
    cs.loginServerKeys->pushKey(newKey);
}

//-----------------------------------------------------------------------

void
ConnectionServer::addNewClient(ClientConnection *cconn, const NetworkId &oid, GameConnection *gconn, const std::string &, bool sendToStarport) {
    static ConnectionServer &cs = instance();
    ClientMap::iterator i = cs.clientMap.find(oid);
    if (i != cs.clientMap.end()) {
        if (cconn->getClient()) {
            WARNING_STRICT_FATAL(true, ("Client already connected, attempting to drop old one\n"));
            dropClient(cconn, "Duplicate Login");
            return;
        } else {
            // stale connection in map
            cs.removeFromClientMap(oid);
        }
    }

    // Create a new entry in the client map
    cs.addToClientMap(oid, cconn);

    // Get the client that was just created
    Client *newClient = cs.getClient(oid);
    NOT_NULL(newClient);

    // select a chat server connection for the client
    // if non exists, then the client will be notified when
    // one starts
    if (!cs.chatServers.empty()) {
        //ChatServerConnection * c = (*chatServers.begin());
        // find chat server with least load
        size_t max = 0xFFFFFFFF;
        ChatServerConnection *candidate = 0;
        std::set<ChatServerConnection *>::const_iterator iter;
        for (iter = cs.chatServers.begin(); iter != cs.chatServers.end(); ++iter) {
            if ((*iter)->getClients().size() <= max) {
                candidate = (*iter);
                max = (*iter)->getClients().size();
            }
        }
        if (candidate) {
            newClient->setChatConnection(candidate);
        }
    }

    // select a cs server connection for the client
    // if non exists, then the client will be notified when
    // one starts
    if (!cs.customerServiceServers.empty()) {
        //ChatServerConnection * c = (*chatServers.begin());
        // find chat server with least load
        size_t max = 0xFFFFFFFF;
        CustomerServiceConnection *candidate = 0;
        std::set<CustomerServiceConnection *>::const_iterator iter;
        for (iter = cs.customerServiceServers.begin(); iter != cs.customerServiceServers.end(); ++iter) {
            if ((*iter)->getClients().size() <= max) {
                candidate = (*iter);
                max = (*iter)->getClients().size();
            }
        }
        if (candidate) {
            newClient->setCustomerServiceConnection(candidate);
        }
    }

    //send the game server a message about this client.
    static const std::string loginTrace("TRACE_LOGIN");
    LOG(loginTrace, ("NewClient(%d, %s, %s)", cconn->getSUID(), oid.getValueString().c_str(), cconn->getAccountName().c_str()));
    NewClient m(oid, cconn->getAccountName(), cconn->getRemoteAddress(), cconn->getIsSecure(), false, cconn->getSUID(), nullptr, cconn->getGameFeatures(), cconn->getSubscriptionFeatures(), cconn->getEntitlementTotalTime(), cconn->getEntitlementEntitledTime(), cconn->getEntitlementTotalTimeSinceLastLogin(), cconn->getEntitlementEntitledTimeSinceLastLogin(), cconn->getBuddyPoints(), cconn->getConsumedRewardEvents(), cconn->getClaimedRewardItems(), cconn->isUsingAdminLogin(), cconn->getCanSkipTutorial(), sendToStarport);
    gconn->send(m, true);
    //@todo move this to ClientConnection.cpp
}

//-----------------------------------------------------------------------

void ConnectionServer::dropClient(ClientConnection *conn, const std::string &description) {
    DEBUG_FATAL(!conn, ("Cannot call dropClient with nullptr connection"));
    if (!conn) { //lint !e774 // boolean within 'if' always evaluates to False //suppresed because this is only relevant in DEBUG builds
        return;
    }

    static ConnectionServer &cs = instance();
    //Client dropped.  Tell game server if they've logged in.
    LOG("ClientDisconnect", ("Dropping client for SUID %d\n", conn->getSUID()));
    LOG("CustomerService", ("Login:%s Dropped Reason: %s. Character: %s (%s). Play time: %s. Active play time: %s", ClientConnection::describeAccount(conn).c_str(), description.c_str(), conn->getCharacterName().c_str(), conn->getCharacterId().getValueString().c_str(), conn->getPlayTimeDuration().c_str(), conn->getActivePlayTimeDuration().c_str()));

    Client *client = conn->getClient();
    if (client) {
        DropClient msg(client->getNetworkId());
        GameConnection *gconn = client->getGameConnection();
        //Don't worry about sending a message to a non-existant game server
        if (gconn) {
            gconn->send(msg, true);
        } else {
            DEBUG_REPORT_LOG(true, ("Could not find game server to drop this client\n"));
        }

        // Remove the entry from the client map
        cs.removeFromClientMap(client->getNetworkId());
    } else {
        //If they aren't connected to the game yet, they're probably on the pending list.
        //@todo ensure we don't need to send a cleanup message to central
//		removePendingCharacter(conn->getSUID());
    }

    // Remove entry from the connected map
    cs.removeFromConnectedMap(conn->getSUID());

    conn->disconnect();
}

//-----------------------------------------------------------------------
// void ConnectionServer::addPendingCharacter(uint32 suid, ClientConnection* conn)
// {
//     if (pendingMap.find(suid) == pendingMap.end())
//         pendingMap[suid] = conn;
//     else
//     {
//         WARNING_STRICT_FATAL(true, ("Attepting to add duplicate pending chatacter"));
//         pendingMap[suid] = conn;
//     }
// }

//-----------------------------------------------------------------------

void ConnectionServer::addConnectedClient(uint32 suid, ClientConnection *conn) {
    static ConnectionServer &cs = instance();
    cs.addToConnectedMap(suid, conn);
}

//-----------------------------------------------------------------------

ClientConnection *ConnectionServer::getClientConnection(const uint32 suid) {
    static ConnectionServer &cs = instance();
    ClientConnection *result = 0;
    SuidMap::const_iterator i = cs.connectedMap.find(suid);
    if (i != cs.connectedMap.end()) {
        result = (*i).second;
    }
    return result;
}

// ----------------------------------------------------------------------

const Service *ConnectionServer::getGameService() {
    static ConnectionServer &cs = instance();
    return cs.gameService;
}

// ----------------------------------------------------------------------

GameConnection *ConnectionServer::getGameConnection(const std::string &sceneName) {
    static ConnectionServer &cs = instance();
    GameServerMap::iterator i = cs.gameServerMap.begin();
    for (; i != cs.gameServerMap.end(); ++i) {
        if (sceneName == (*i).second->getSceneName()) {
            return (*i).second;
        }
    }
    return nullptr;
}

// ----------------------------------------------------------------------
void ConnectionServer::handleConnectionServerIdMessage(const ConnectionServerId &m) {
    // Connection established with central server.  Set everything up.
    m_id = m.getId();

    const Service *const servicePrivate = getClientServicePrivate();
    const Service *const servicePublic = getClientServicePublic();
    FATAL(servicePrivate == nullptr && servicePublic == nullptr, ("No client service is active!"));

    const Service *const g = getGameService();
    FATAL(g == nullptr, ("No game service is active!"));
    const Service *const c = getChatService();
    const Service *const cs = getCustomerService();
    const uint16 pingPort = getPingPort();

    if ((servicePrivate != nullptr || servicePublic != nullptr) && g !=
                                                                   nullptr) //lint !e774 // always evaluates to false // suppresed because the FATAL macro triggers this lint warning
    {
        uint16 chatPort = 0;
        uint16 csPort = 0;
        if (c) {
            chatPort = c->getBindPort();
        }

        if (cs) {
            csPort = cs->getBindPort();
        }

        uint16 publicPort = 0;
        if (servicePublic) {
            publicPort = servicePublic->getBindPort();
        }

        uint16 privatePort = 0;
        if (servicePrivate) {
            privatePort = servicePrivate->getBindPort();
        }

        std::string clientServicePublicBindAddress = ConfigConnectionServer::getPublicBindAddress();
        if (clientServicePublicBindAddress.empty()) {
            clientServicePublicBindAddress = NetworkHandler::getHostName();
        }

        NOT_NULL(gameService);
        NOT_NULL(chatService);
        NOT_NULL(customerService);

        const NewCentralConnectionServer ncs(gameService->getBindAddress(), clientServicePublicBindAddress, chatService->getBindAddress(), customerService->getBindAddress(), privatePort, publicPort, g->getBindPort(), chatPort, csPort, pingPort, ConfigConnectionServer::getConnectionServerNumber());
        sendToCentralProcess(ncs);
    } else {
        FATAL(true, ("Error in connection server startup"));
    }
}

// ----------------------------------------------------------------------

void
ConnectionServer::receiveMessage(const MessageDispatch::Emitter &source, const MessageDispatch::MessageBase &message) {
    // it's reasonably safe to cast, message type verified
    // determine message type

    const uint32 messageType = message.getType();

    switch (messageType) {
        case constcrc("GameConnectionOpened") : {
            DEBUG_REPORT_LOG(true, ("Game Connection opened\n"));
            break;
        }
        case constcrc("GameConnectionClosed") : {
            //@todo handle case where game server drops and we have users connected to it.
            //Drop all connected clients.
            const GameConnection &downConnection = static_cast<const GameConnection &>(source);
            DEBUG_REPORT_LOG(true, ("Game Server connection went down.  Dropping clients.\n"));
            //remove Game Conection from list

            PseudoClientConnection::gameConnectionClosed(&downConnection);

            GameServerMap::iterator j = gameServerMap.find(downConnection.getGameServerId());
            if (j != gameServerMap.end()) {
                gameServerMap.erase(j);
            }
            break;
        }
        case constcrc("CentralConnectionOpened") : {
            DEBUG_REPORT_LOG(true, ("Opened connection with central\n"));
            centralConnection = const_cast<CentralConnection *>(static_cast<const CentralConnection *>(&source));//lint !e826 // info: Suspiscious pointer-to-pointer conversion (area too small)

            if (ConfigConnectionServer::getStartPublicServer()) {
                s_clientServiceSetup->port = ConfigConnectionServer::getClientServicePortPublic();
                clientServicePublic = new Service(ConnectionAllocator<ClientConnection>(), *s_clientServiceSetup);
            }

            s_clientServiceSetup->port = ConfigConnectionServer::getClientServicePortPrivate();
            clientServicePrivate = new Service(ConnectionAllocator<ClientConnection>(), *s_clientServiceSetup);

            connectToMessage("ClientConnectionOpened");
            connectToMessage("ClientConnectionClosed");
            break;
        }
        case constcrc("CentralConnectionClosed") : {
            centralConnection = const_cast<CentralConnection *>(static_cast<const CentralConnection *>(&source));//lint !e826 // info: Suspiscious pointer-to-pointer conversion (area too small)
            setDone("CentralConnectionClosed: %s", centralConnection ? centralConnection->getDisconnectReason().c_str()
                                                                     : "");
            centralConnection = 0;
            DEBUG_REPORT_LOG(true, ("CentralDied.  So we will too\n"));
            //@todo Drop all pending clients.
            break;
        }
        case constcrc("ClientConnectionOpened") : {
            DEBUG_REPORT_LOG(true, ("Opened connection with client\n"));
            break;
        }
        case constcrc("ClientConnectionClosed") : {
            DEBUG_REPORT_LOG(true, ("Client is Dropping connection\n"));
            ClientConnection *cconn = const_cast<ClientConnection *>(static_cast<const ClientConnection *>(&source));//lint !e826 // info: Suspiscious pointer-to-pointer conversion (area too small)

            //tell CentralServer
            if (centralConnection) {
                GenericValueTypeMessage <StationId> const msg("ClientConnectionClosed", cconn->getSUID());
                centralConnection->send(msg, true);
            }

            //Client dropped.  Tell game server if they've logged in.
            Client *client = cconn->getClient();
            if (client) {
                DropClient msg(client->getNetworkId());
                GameConnection *gconn = client->getGameConnection();
                //Don't worry about sending a message to a non-existant game server
                if (gconn) {
                    gconn->send(msg, true);
                }

                // Remove the entry from the client map
                removeFromClientMap(client->getNetworkId());
            } else {
                // TODO: wtf is this? @Darth, fix it!
                //If they aren't connected to the game yet, they're probably on the pending list.
                //removePendingCharacter(cconn->getSUID());
            }
            removeFromConnectedMap(cconn->getSUID());
            break;
        }
        case constcrc("ConnectionServerId") : {
            Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
            ConnectionServerId m(ri);
            handleConnectionServerIdMessage(m);
            break;
        }
        case constcrc("ConnectionKeyPush") : {
            Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
            ConnectionKeyPush pk(ri);
            loginServerKeys->pushKey(pk.getKey());
            break;
        }
        case constcrc("LoginKeyPush") : {
            Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
            const LoginKeyPush k(ri);
            loginServerKeys->pushKey(k.getKey());
            break;
        }
        case constcrc("CharacterListMessage") : {
            Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
            const CharacterListMessage msg(ri);
            WARNING_STRICT_FATAL(true, ("CharacterListMessage is deprecated on the ConnectionServer -- fix whoever is sending it.\n"));
            break;
        }
        case constcrc("ConnectionCreateCharacterSuccess") : {
            Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
            const ConnectionCreateCharacterSuccess msg(ri);
            LOG("TraceCharacterCreation", ("Received ConnectionCreateCharacterSuccess for %d", msg.getStationId()));
            ClientConnection *const client = getClientConnection(msg.getStationId());
            if (client) {
                const ClientCreateCharacterSuccess m(msg.getNetworkId());
                client->send(m, true);
            } else {
                LOG("CustomerService", ("CharacterTransfer: Trying to deliver ConnectionCreateCharacterSuccess to PsuedoClientConnection(%d)", msg.getStationId()));
                PseudoClientConnection::tryToDeliverMessageTo(static_cast<unsigned int>(msg.getStationId()), static_cast<const GameNetworkMessage &>(message).getByteStream());
            }
            break;
        }
        case constcrc("ConnectionCreateCharacterFailed") : {
            Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
            const ConnectionCreateCharacterFailed msg(ri);
            ClientConnection *client = getClientConnection(msg.getStationId());
            if (client) {
                client->setHasRequestedCharacterCreate(false);

                ClientCreateCharacterFailed m(msg.getName(), msg.getErrorMessage()); //lint !e1013 !e1055 !e746 (Symbol 'getErrorMessage' not a member of class 'const ConnectionCreateCharacterFailed') // supressed because it IS a member of that class. //lint !e1055 //lint !e746
                client->send(m, true);
            } else {
                PseudoClientConnection::tryToDeliverMessageTo(static_cast<unsigned int>(msg.getStationId()), static_cast<const GameNetworkMessage &>(message).getByteStream());
            }
            break;
        }
        case constcrc("NewCharacterCreated") : {
            Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
            GenericValueTypeMessage <StationId> const msg(ri);
            ClientConnection *client = getClientConnection(msg.getValue());
            if (client) {
                // don't allow this client to request another character create;
                // this will forced the client to disconnect and reconnect at which time
                // a check will be done (taking the newly created character into account)
                // to see if the client is allowed to create another character on this account
                client->setHasCreatedCharacter(true);
            }
            break;
        }
        case constcrc("RandomNameResponse") : {
            Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
            RandomNameResponse connMsg(ri);

            ClientConnection *cconn = getClientConnection(connMsg.getStationId());
            ClientRandomNameResponse cnr(connMsg.getCreatureTemplate(), connMsg.getName(), connMsg.getErrorMessage());//lint !e1013 (Symbol 'getErrorMessage' not a member of class 'RandomNameResponse') // supressed because it IS a member of that class.
            if (cconn) {
                cconn->send(cnr, true);
            }

            break;
        }
        case constcrc("VerifyAndLockNameResponse") : {
            Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
            VerifyAndLockNameResponse connMsg(ri);

            ClientConnection *cconn = getClientConnection(connMsg.getStationId());
            ClientVerifyAndLockNameResponse cvalnr(connMsg.getCharacterName(), connMsg.getErrorMessage());
            if (cconn) {
                cconn->send(cvalnr, true);
            }

            break;
        }
        case constcrc("ChatServerConnectionOpened") : {
            ChatServerConnection *c = const_cast<ChatServerConnection *>(static_cast<const ChatServerConnection *>(&source));//lint !e826 suspiscious pointer-to-pointer conversion // suppressed, you bet it is
            IGNORE_RETURN(chatServers.insert(c));
            break;
        }
        case constcrc("ChatServerConnectionClosed") : {
            ChatServerConnection *c = const_cast<ChatServerConnection *>(static_cast<const ChatServerConnection *>(&source));//lint !e826 suspiscious pointer-to-pointer conversion // suppressed, you bet it is
            std::set<ChatServerConnection *>::iterator f = chatServers.find(c);
            if (f != chatServers.end()) {
                // migrate players that were on this chat server
                // to another chat server

                // now remove the server from the set
                chatServers.erase(f);
                if (!chatServers.empty()) {
                    std::set<ChatServerConnection *>::iterator ic = chatServers.begin();

                    const std::set<Client *> &clients = c->getClients();
                    std::set<Client *>::const_iterator i;
                    for (i = clients.begin(); i != clients.end(); ++i) {
                        ChatServerConnection *newConn = (*ic);
                        Client *cl = (*i);
                        cl->setChatConnection(newConn);
                        ++ic;
                        if (ic == chatServers.end()) {
                            ic = chatServers.begin();
                        }
                    }
                } else {
                    const std::set<Client *> &clients = c->getClients();
                    std::set<Client *>::const_iterator i;
                    for (i = clients.begin(); i != clients.end(); ++i) {
                        (*i)->setChatConnection(nullptr);
                    }
                }
            }
            break;
        }
        case constcrc("CustomerServiceConnectionOpened") : {
            CustomerServiceConnection *c = const_cast<CustomerServiceConnection *>(static_cast<const CustomerServiceConnection *>(&source));//lint !e826 suspiscious pointer-to-pointer conversion // suppressed, you bet it is
            IGNORE_RETURN(customerServiceServers.insert(c));
            break;
        }
        case constcrc("CustomerServiceConnectionClosed") : {
            CustomerServiceConnection *c = const_cast<CustomerServiceConnection *>(static_cast<const CustomerServiceConnection *>(&source));//lint !e826 suspiscious pointer-to-pointer conversion // suppressed, you bet it is
            std::set<CustomerServiceConnection *>::iterator f = customerServiceServers.find(c);
            if (f != customerServiceServers.end()) {
                // migrate players that were on this chat server
                // to another chat server

                // now remove the server from the set
                customerServiceServers.erase(f);
                if (!customerServiceServers.empty()) {
                    std::set<CustomerServiceConnection *>::iterator ic = customerServiceServers.begin();

                    const std::set<Client *> &clients = c->getClients();
                    std::set<Client *>::const_iterator i;
                    for (i = clients.begin(); i != clients.end(); ++i) {
                        CustomerServiceConnection *newConn = (*ic);
                        Client *cl = (*i);
                        cl->setCustomerServiceConnection(newConn);
                        ++ic;
                        if (ic == customerServiceServers.end()) {
                            ic = customerServiceServers.begin();
                        }
                    }
                } else {
                    const std::set<Client *> &clients = c->getClients();
                    std::set<Client *>::const_iterator i;
                    for (i = clients.begin(); i != clients.end(); ++i) {
                        (*i)->setCustomerServiceConnection(nullptr);
                    }
                }
            }
            break;
        }
        case constcrc("GameServerForLoginMessage") : {
            Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
            const GameServerForLoginMessage msg(ri);

            ClientConnection *client = getClientConnection(msg.getStationId());

            // see if the client is for the same character as the login message.
            // this is required to prevent admin login via the CS Tool from
            // disconnecting other characters logged in from the same account
            // (but not the same character) as the character we're administratively
            // logging in.
            bool clientIsForSameCharacterId = false;
            if (client) {
                clientIsForSameCharacterId = client->getCharacterId() == msg.getCharacterId();
            }

            bool handledByPseudoClient = false;
            // if the character id in the message doesn't match the character id for
            // the existing client, it may be for a pseudoclient.  Check, and if so,
            // handle there.
            if (!clientIsForSameCharacterId) {
                PseudoClientConnection *pcc = PseudoClientConnection::getPseudoClientConnection(msg.getCharacterId());
                // hand off to the PCC only if we do have a pseudoclient, and either we don't have a client, or
                // the pseudoclient has a tool id, telling us that it's for cs tool login.
                if (pcc && ((!client) || (pcc->getTransferCharacterData().getCSToolId() > 0))) {
                    handledByPseudoClient = true;
                    bool result;
                    result = PseudoClientConnection::tryToDeliverMessageTo(msg.getStationId(), static_cast<const GameNetworkMessage &>(message).getByteStream());
                    UNREF(result);
                    DEBUG_REPORT_LOG(!result, ("Received GameServerForLoginMessage for %lu, who was not connected.\n", msg.getStationId()));
                }
            }

            if (client && (!handledByPseudoClient)) {
                static const std::string loginTrace("TRACE_LOGIN");
                LOG(loginTrace, ("GameServerForLoginMessage(%d, %s)", client->getSUID(), client->getCharacterId().getValueString().c_str()));
                client->handleGameServerForLoginMessage(msg.getServer());
            }
            break;
        }
        case constcrc("ValidateCharacterForLoginReplyMessage") : {
            Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
            const ValidateCharacterForLoginReplyMessage msg(ri);

            ClientConnection *cconn = getClientConnection(msg.getSuid());
            if (!cconn) {
                DEBUG_REPORT_LOG(true, ("Received ValidateCharacterForLoginReplyMessage for account %lu, which is no longer connected.\n", msg.getSuid()));
            } else {
                cconn->onCharacterValidated(msg.getApproved(), msg.getCharacterId(), Unicode::wideToNarrow(msg.getCharacterName()), msg.getContainerId(), msg.getScene(), msg.getCoordinates());
            }
            break;
        }
        case constcrc("ValidateAccountReplyMessage") : {
            Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
            const ValidateAccountReplyMessage msg(ri);

            ClientConnection *cconn = getClientConnection(msg.getStationId());
            if (!cconn) {
                DEBUG_REPORT_LOG(true, ("Received ValidateAccountReplyMessage for account %lu, which is no longer connected.\n", msg.getStationId()));
            } else {
                cconn->onIdValidated(msg.getCanLogin(), msg.getCanCreateRegular(), msg.getCanCreateJedi(), msg.getCanSkipTutorial(), msg.getConsumedRewardEvents(), msg.getClaimedRewardItems());
            }
            break;
        }
        case constcrc("SetConnectionServerPublic") : {
            Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
            SetConnectionServerPublic p(ri);//lint !e40 !e522 !e10 // Undeclared identifier 'SetConnectionServerPublic' // suppressed because it IS declared
            bool statusChanged = false;
            DEBUG_REPORT_LOG(true, ("Conn Server: attempting to chang status\n"));
            if (p.getIsPublic())//lint !e40 !e1013 !e10 // Undeclared identifier 'p' // suppressed because it IS declared
            {
                if (!clientServicePublic) {
                    statusChanged = true;
                }
            } else {
                delete clientServicePublic;
                clientServicePublic = 0;
                statusChanged = true;
            }

            if (statusChanged && centralConnection) {
                const Service *publicService = getClientServicePublic();
                const Service *privateService = getClientServicePrivate();
                if (publicService || privateService) {
                    uint16 publicServicePort = 0;
                    uint16 privateServicePort = 0;
                    if (publicService) {
                        publicServicePort = publicService->getBindPort();
                    }
                    if (privateService) {
                        privateServicePort = privateService->getBindPort();
                    }
                    const UpdateConnectionServerStatus ucs(publicServicePort, privateServicePort);
                    centralConnection->send(ucs, true);
                }
            }
            break;
        }//lint !e529 // Symbol 'ri' not subsequently referenced // suppressed because it IS referenced. I think lint is very confused for some reason.
        case constcrc("ProfilerOperationMessage") : {
            Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
            ProfilerOperationMessage msg(ri);
            unsigned int processId = msg.getProcessId();
            if (!processId) {
                Profiler::handleOperation(msg.getOperation().c_str());
            }

            break;
        }
        case constcrc("ExcommunicateGameServerMessage") : {
            Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
            ExcommunicateGameServerMessage msg(ri);

            LOG("GameGameConnect", ("Told to drop connection to %lu by Central", msg.getServerId()));

            GameConnection *conn = getGameConnection(msg.getServerId());
            if (conn) {
                conn->disconnect();
            }

            break;
        }
        case constcrc("CntrlSrvDropDupeConns") : {
            Archive::ReadIterator ri = static_cast<const GameNetworkMessage &>(message).getByteStream().begin();
            GenericValueTypeMessage <std::pair<uint32, std::string>> const msg(ri);

            ClientConnection *client = getClientConnection(msg.getValue().first);
            if (client && !client->isUsingAdminLogin() && !client->getIsSecure()) {
                std::string s = "New Connection on galaxy ";
                s += msg.getValue().second;

                dropClient(client, s);
            }
            break;
        }
    }
}

//-----------------------------------------------------------------------

void ConnectionServer::install() {
    s_connectionServer = new ConnectionServer;

    char tmp[128] = {"\0"};
    IGNORE_RETURN(snprintf(tmp, sizeof(tmp), "ConnectionServer:%d", Os::getProcessId()));
    SetupSharedLog::install(tmp);
    s_connectionServer->setupConnections();
    s_connectionServer->m_metricsData = new ConnectionServerMetricsData;
    MetricsManager::install(s_connectionServer->m_metricsData, true, "ConnectionServer", "", ConfigConnectionServer::getConnectionServerNumber());
    DataTableManager::install();
    AdminAccountManager::install(ConfigConnectionServer::getAdminAccountDataTable());
}

//-----------------------------------------------------------------------

void ConnectionServer::remove() {
    MetricsManager::remove();
    delete s_connectionServer->m_metricsData;
    s_connectionServer->m_metricsData = 0;

    // explicitly delete all connections that were setup from setupConnections rather than letting
    // Connection::remove do it.  There are connections that require a valid s_connectionServer which is deleted
    // and set to nullptr.
    s_connectionServer->unsetupConnections();

    SetupSharedLog::remove();

    delete s_connectionServer;
    s_connectionServer = 0;
}

//-----------------------------------------------------------------------

void ConnectionServer::run(void) {
    static const bool shouldSleep = ConfigConnectionServer::getShouldSleep();
    static ConnectionServer &cserver = instance();
    DEBUG_FATAL(!cserver.m_metricsData, ("Connection server not installed properly"));

    unsigned long startTime = Clock::timeMs();
    Clock::setFrameRateLimit(50.0f);

    LOG("ServerStartup", ("ConnectionServer starting on %s", NetworkHandler::getHostName().c_str()));
    while (!cserver.done) {
        PROFILER_AUTO_BLOCK_DEFINE("main loop");

        //bool barrierReached = true;

        do {
            {
                PROFILER_AUTO_BLOCK_DEFINE("Os::update");
                if (!Os::update()) {
                    cserver.setDone("Os condition (Parent pid change)");
                }
            }

            {
                PROFILER_AUTO_BLOCK_DEFINE("NetworkHandler::update1");
                NetworkHandler::update();
            }

            {
                GenericValueTypeMessage <std::pair<uint16, uint32>> const syncStampMessage("SetSyncStamp", std::make_pair(static_cast<uint16>(UdpMisc::LocalSyncStampShort()), static_cast<uint32>(UdpMisc::LocalSyncStampLong())));
                GameServerMap::iterator end = cserver.gameServerMap.end();
                for (GameServerMap::iterator iter = cserver.gameServerMap.begin(); iter != end; ++iter) {
                    iter->second->send(syncStampMessage, true);
                }
            }

            {
                PROFILER_AUTO_BLOCK_DEFINE("NetworkHandler::dispatch");
                NetworkHandler::dispatch();
            }

            {
                PROFILER_AUTO_BLOCK_DEFINE("update");
                cserver.update();
            }

            {
                PROFILER_AUTO_BLOCK_DEFINE("NetworkHandler::update2");
                NetworkHandler::update();
            }

            {
                PROFILER_AUTO_BLOCK_DEFINE("MetricsManager::update");
                unsigned long curTime = Clock::timeMs();
                MetricsManager::update(static_cast<float>(curTime - startTime));
                startTime = curTime;
            }

            if (shouldSleep) {
                PROFILER_AUTO_BLOCK_DEFINE("Os::sleep");
                Os::sleep(1);
            }
        } while (!cserver.done);

        NetworkHandler::clearBytesThisFrame();

        cserver.updateRecoveringClientList(static_cast<uint>(Clock::frameTime() * 1000.0f));
    }
}

// ----------------------------------------------------------------------
/**
 * Invoked every frame to do whatever updates are needed
 */
void ConnectionServer::update() {
    if (centralConnection) {
        static Timer t(5.0f);
        if (t.updateZero(Clock::frameTime())) {
            // Update the population on the central server
            updatePopulationOnCentralServer();

            if (getNumberOfClients() >= ConfigConnectionServer::getMaxClients() - 1) {
                //@todo need an Alert here
                WARNING(true, ("We've reached maximum client capacity on a connection server with %d clients", getNumberOfClients()));
            }
        }
    }

    /*if (m_sessionApiClient)
    {
        m_sessionApiClient->update();
    }*/

    static const int ping_throttle_max = 1024;

    static char buffer[4];

    for (int throttle = 0; pingSocket->canRecv() && throttle < ping_throttle_max; ++throttle) {
        Address addr;
        const uint32 count = pingSocket->recvFrom(addr, buffer, 4);
        m_pingTrafficNumBytes += static_cast<int>(count);
        if (m_pingTrafficNumBytes < 0) {
            m_pingTrafficNumBytes = 0;
        }
        IGNORE_RETURN(pingSocket->sendTo(addr, buffer, count));
    }
}

// ----------------------------------------------------------------------

/**
 * The recovering client list holds players who are trying to recover from a server
 * crash.  If another game server takes authority within a few frames, the player
 * is OK and we don't do anything.  If no game server takes authority for them,
 * we drop them and force them to log in again.
 */
void ConnectionServer::updateRecoveringClientList(uint elapsedTime) {
    if (!m_recoveringClientList.empty()) {
        m_recoverTime += elapsedTime;

        static std::set <NetworkId> listPlayersDropped;
        for (RecoveringClientListType::iterator i = m_recoveringClientList.begin();
             i != m_recoveringClientList.end();) {
            if (m_recoverTime > i->first) {
                Client *client = getClient(i->second);
                if (client) {
                    if (client->getGameConnection()) {
                        DEBUG_REPORT_LOG(true, ("Player %s recovered from a server crash and will not be dropped.\n", i->second.getValueString().c_str()));
                    } else {
                        LOG("Network", ("Dropping player %s because game server crashed and no other server took authority.\n", i->second.getValueString().c_str()));
                        dropClient(client->getClientConnection(), "Game Server Crash");

                        IGNORE_RETURN(listPlayersDropped.insert(i->second));
                    }
                }
                i = m_recoveringClientList.erase(i);
            } else {
                ++i;
            }
        }

        if (!listPlayersDropped.empty()) {
            GenericValueTypeMessage <std::set<NetworkId>> const m("PlayerDroppedFromGameServerCrash", listPlayersDropped);

            // let one of the game server know that we dropped the player(s) due to a game server crash
            GameConnection *const anyGameConnection = getAnyGameConnection();
            if (anyGameConnection) {
                anyGameConnection->send(m, true);
            }

            // let CentralServer know that we dropped the player(s) due to a game server crash
            sendToCentralProcess(m);

            listPlayersDropped.clear();
        }

        if (m_recoveringClientList.empty()) {
            m_recoverTime = 0;
        } // prevent rollover and other problems I don't want to worry about
    }
}

// ----------------------------------------------------------------------

void ConnectionServer::addRecoveringClient(const NetworkId &networkId) {
    instance().m_recoveringClientList.push_back(std::pair<int, NetworkId>(static_cast<int>(instance().m_recoverTime) +
                                                                          ConfigConnectionServer::getCrashRecoveryTimeout(), networkId));//lint !e737 !e713 !e1703 !e1025 // going nuts over unsigned/signed conversion and type mismatch on template
}

// ----------------------------------------------------------------------

void ConnectionServer::unsetupConnections() {
    // remove all ClientConnection objects so when Connection::remove() is called we don't crash since
    // ConnectionServer no longer exists
    while (!instance().connectedMap.empty()) {
        SuidMap::iterator i = instance().connectedMap.begin();
        if (i != instance().connectedMap.end()) {
            ClientConnection *c = (*i).second;
            uint32 suid = (*i).first;
            IGNORE_RETURN(instance().connectedMap.erase(suid));
            if (c) {
                ConnectionServer::dropClient(c, "ConnectionServer shutting down.");
                delete c;
            }
        }
    }

    if (customerService) {
        delete customerService;
        customerService = 0;
    }

    if (chatService) {
        delete chatService;
        chatService = 0;
    }

    if (centralConnection) {
        delete centralConnection;
        centralConnection = 0;
    }

    if (gameService) {
        delete gameService;
        gameService = 0;
    }
}
//-----------------------------------------------------------------------

void ConnectionServer::setupConnections() {
    // set up message connections
    connectToMessage("ConnectionGameServerConnect");
    connectToMessage("SetConnectionServerPublic");

    // set up port to listen for clients, central, and game servers

    connectToMessage("CentralConnectionOpened");
    connectToMessage("CentralConnectionClosed");

    NetworkSetupData setup;
    setup.port = ConfigConnectionServer::getGameServicePort();
    setup.bindInterface = ConfigConnectionServer::getGameServiceBindInterface();
    setup.maxConnections = 1000;

    gameService = new Service(ConnectionAllocator<GameConnection>(), setup);
    connectToMessage("GameConnectionOpened");
    connectToMessage("GameConnectionClosed");

    // connect to central server
    centralConnection = new CentralConnection(ConfigConnectionServer::getCentralServerAddress(), ConfigConnectionServer::getCentralServerPort());

    setup.port = 0;
    setup.bindInterface = ConfigConnectionServer::getChatServiceBindInterface();
    chatService = new Service(ConnectionAllocator<ChatServerConnection>(), setup);
    connectToMessage("ChatServerConnectionOpened");
    connectToMessage("ChatServerConnectionClosed");

    setup.bindInterface = ConfigConnectionServer::getCustomerServiceBindInterface();
    customerService = new Service(ConnectionAllocator<CustomerServiceConnection>(), setup);
    connectToMessage("CustomerServiceConnectionOpened");
    connectToMessage("CustomerServiceConnectionClosed");

    connectToMessage("ConnectionServerId");
    connectToMessage("ConnectionKeyPush");
    connectToMessage("LoginKeyPush");
    connectToMessage("CharacterListMessage");

    //Create Characters Messages
    connectToMessage("ClientCreateCharacter");
    connectToMessage("ConnectionCreateCharacterSuccess");
    connectToMessage("ConnectionCreateCharacterFailed");
    connectToMessage("NewCharacterCreated");
    connectToMessage("GameServerForLoginMessage");

    // name query messages
    connectToMessage("ClientRandomNameRequest"); // from client
    connectToMessage("RandomNameResponse"); // from game server

    connectToMessage("ClientVerifyAndLockNameRequest"); // from client
    connectToMessage("VerifyAndLockNameResponse"); // from game server

    connectToMessage("ValidateCharacterForLoginReplyMessage");
    connectToMessage("ValidateAccountReplyMessage");

    connectToMessage("ProfilerOperationMessage");
    connectToMessage("CentralConnectionClosed");
    connectToMessage("CentralConnectionOpened");
    connectToMessage("ChunkCompleteMessage");
    connectToMessage("FrameEndMessage");
    connectToMessage("GameConnectionClosed");
    connectToMessage("GameGameServerConnect");
    connectToMessage("GameServerReadyMessage");
    connectToMessage("GameServerUniverseLoadedMessage");
    connectToMessage("PersistedPlayerMessage");
    connectToMessage("PreloadListMessage");
    connectToMessage("PreloadRequestCompleteMessage");
    connectToMessage("ProfilerOperationMessage");
    connectToMessage("RequestGameServerForLoginMessage");
    connectToMessage("RequestSceneTransfer");
    connectToMessage("ShutdownMessage");
    connectToMessage("TaskConnectionOpened");
    connectToMessage("UnloadedPlayerMessage");
    connectToMessage("WatcherConnectionClosed");
    connectToMessage("WatcherConnectionOpened");
    connectToMessage("ExcommunicateGameServerMessage");
    connectToMessage("CntrlSrvDropDupeConns");
}

//----------------------------------------------------------------------

uint16 ConnectionServer::getPingPort() {
    static ConnectionServer &cs = instance();
    return cs.pingSocket->getBindAddress().getHostPort();
}

//-----------------------------------------------------------------------

Client *ConnectionServer::getClient(const NetworkId &oid) {
    static ConnectionServer &cs = instance();
    ClientMap::const_iterator iter = cs.clientMap.find(oid);
    if (iter != cs.clientMap.end()) {
        return (*iter).second;
    }
    return 0;
}

//-----------------------------------------------------------------------

const ConnectionServer::ClientMap &ConnectionServer::getClientMap() {
    return instance().clientMap;
}

//-----------------------------------------------------------------------

GameConnection *ConnectionServer::getGameConnection(uint32 gameServerId) {
    static ConnectionServer &cs = instance();
    const GameServerMap::const_iterator i = cs.gameServerMap.find(gameServerId);
    if (i != cs.gameServerMap.end()) {
        return (*i).second;
    }

    return nullptr;
}

//-----------------------------------------------------------------------

GameConnection *ConnectionServer::getAnyGameConnection() {
    static ConnectionServer &cs = instance();
    if (!cs.gameServerMap.empty()) {
        return cs.gameServerMap.begin()->second;
    }

    return nullptr;
}

//-----------------------------------------------------------------------

int ConnectionServer::getPingTrafficNumBytes() {
    static ConnectionServer &cs = instance();
    return cs.m_pingTrafficNumBytes;
}

//-----------------------------------------------------------------------

int ConnectionServer::getNumberOfClients() {
    static ConnectionServer &cs = instance();
    return static_cast<int>(cs.connectedMap.size());
}

//-----------------------------------------------------------------------

int ConnectionServer::getNumberOfFreeTrials() {
    static ConnectionServer &cs = instance();
    return static_cast<int>(cs.freeTrials.size());
}

//-----------------------------------------------------------------------

int ConnectionServer::getNumberOfGameServers() {
    static ConnectionServer &cs = instance();
    return static_cast<int>(cs.gameServerMap.size());
}

//-----------------------------------------------------------------------

void ConnectionServer::removeConnectedCharacter(uint32 suid) {
    static ConnectionServer &cs = instance();
    cs.removeFromConnectedMap(suid);
}
//-----------------------------------------------------------------------

void ConnectionServer::sendToCentralProcess(const GameNetworkMessage &msg) {
    static ConnectionServer &cs = instance();
    if (cs.centralConnection) {
        cs.centralConnection->send(msg, true);
    } else {
        WARNING(true, ("Connection tried to send a message Central, but there is no Central Connection"));
    }
}

//-----------------------------------------------------------------------

CentralConnection *ConnectionServer::getCentralConnection() {
    static ConnectionServer &cs = instance();
    return cs.centralConnection;
}

//-----------------------------------------------------------------------

void ConnectionServer::installSessionValidation() {
    /*int i = 0;
    std::vector<char const *> sessionServers;
    int const numberOfSessionServers = ConfigConnectionServer::getNumberOfSessionServers();
    for (i = 0; i < numberOfSessionServers; ++i)
    {
        char const * const p = ConfigConnectionServer::getSessionServer(i);
        if (p)
        {
            REPORT_LOG(true, ("Using session server %s\n", p));
            sessionServers.push_back(p);
        }
    }

    // if there were none specified, use defaults
    FATAL(i == 0, ("No session servers specified for session API"));
    m_sessionApiClient = new SessionApiClient(&sessionServers[0], i);*/
    return;
}

// ----------------------------------------------------------------------

void ConnectionServer::addToClientMap(const NetworkId &oid, ClientConnection *cconn) {
    Client *newClient = new Client(cconn, oid);

    clientMap[oid] = newClient;

    //associate the ClientConnection object.
    cconn->setClient(newClient);
}

// ----------------------------------------------------------------------

void ConnectionServer::removeFromClientMap(const NetworkId &oid) {
    ClientMap::iterator i = clientMap.find(oid);
    if (i != clientMap.end()) {
        clientMap.erase(i);
    }
}

//-----------------------------------------------------------------------

void ConnectionServer::addToConnectedMap(uint32 suid, ClientConnection *cconn) {
    SuidMap::iterator i = connectedMap.find(suid);
    if (i == connectedMap.end()) {
        connectedMap[suid] = cconn;
    } else {
        WARNING_STRICT_FATAL(true, ("Attepting to add duplicate connected chatacter"));
        connectedMap[suid] = cconn;
    }

    if (((cconn->getSubscriptionFeatures() & ClientSubscriptionFeature::FreeTrial) != 0) &&
        ((cconn->getSubscriptionFeatures() & ClientSubscriptionFeature::Base) == 0)) {
        freeTrials.insert(suid);
    }

    // Update the population on the CentralServer immediately
    // since we are trying to avoid people "rushing" the server
    updatePopulationOnCentralServer();
}

// ----------------------------------------------------------------------

void ConnectionServer::removeFromConnectedMap(uint32 suid) {
    SuidMap::iterator i = connectedMap.find(suid);
    if (i != connectedMap.end()) {
        connectedMap.erase(i);
    }

    FreeTrialsSet::iterator j = freeTrials.find(suid);
    if (j != freeTrials.end()) {
        freeTrials.erase(j);
    }

    // We could update the CentralServer population but people
    // leaving the server are not as important as people connecting
    // and so we will wait for update() to handle things
}

// ----------------------------------------------------------------------

void ConnectionServer::updatePopulationOnCentralServer() {
    if (centralConnection) {
        // Total number of clients and how many of those are free trials
        const int numPlayers = getNumberOfClients();
        const int numFreeTrial = getNumberOfFreeTrials();

        // We are concerned about too many people piling up at the beginning
        // of the tutorial, so count how many players could be a problem
        int numPlayersEmptyScene = 0;
        int numPlayersTutorialScene = 0;
        int numPlayersFalconScene = 0;

        // Walk through the clients and evaluate what scene they are in
        SuidMap::const_iterator i;
        for (i = connectedMap.begin(); i != connectedMap.end(); ++i) {
            const ClientConnection *const conn = (*i).second;
            const Client *const client = conn->getClient();

            if (client && client->getGameConnection()) {
                const std::string &scene = client->getGameConnection()->getSceneName();

                if (scene.empty()) {
                    numPlayersEmptyScene += 1;
                } else if (scene == SCENE_NAME_TUTORIAL) {
                    numPlayersTutorialScene += 1;
                } else if (scene.substr(0, SCENE_NAME_FALCON_PREFIX.length()) == SCENE_NAME_FALCON_PREFIX) {
                    numPlayersFalconScene += 1;
                }
            } else {
                numPlayersEmptyScene += 1;
            }
        }

        const UpdatePlayerCountMessage msg(false, numPlayers, numFreeTrial, numPlayersEmptyScene, numPlayersTutorialScene, numPlayersFalconScene);
        centralConnection->send(msg, true);
    }
}

// ----------------------------------------------------------------------

SessionApiClient *ConnectionServer::getSessionApiClient() {
    // this is causing crashes when ConnectionServer is shutdown and something calls this function
    // because instance() returns 0.
    //if (s_connectionServer)
    //{
    //	return instance().m_sessionApiClient;
    //}
    //else
    //{
    return 0;
    //}
}

// ----------------------------------------------------------------------

void ConnectionServer::setDone(char const *reasonfmt, ...) {
    if (!done) {
        char reason[1024];
        va_list ap;
        va_start(ap, reasonfmt);
        IGNORE_RETURN(_vsnprintf(reason, sizeof(reason), reasonfmt, ap));//lint !e530 Symbol 'ap' not initialized
        reason[sizeof(reason) - 1] = '\0';

        LOG("ServerShutdown", ("ConnectionServer (pid %d) shutdown, reason: %s", static_cast<int>(Os::getProcessId()), reason));

        REPORT_LOG(true, ("ConnectionServer (pid %d) shutdown, reason: %s\n", static_cast<int>(Os::getProcessId()), reason));

        done = true;
        va_end(ap);
    }
}

// ======================================================================
