Merge branch 'master' of git://github.com/mbruker/Cockatrice

This commit is contained in:
Daenyth 2013-02-27 12:36:14 -05:00
commit 347d30a84b
44 changed files with 673 additions and 371 deletions

View file

@ -128,8 +128,8 @@ void IslInterface::initServer()
while (roomIterator.hasNext()) {
Server_Room *room = roomIterator.next().value();
room->usersLock.lockForRead();
room->gamesMutex.lock();
room->getInfo(*event.add_room_list(), true, true, false, false);
room->gamesLock.lockForRead();
room->getInfo(*event.add_room_list(), true, true, false);
}
IslMessage message;
@ -150,7 +150,7 @@ void IslInterface::initServer()
roomIterator.toFront();
while (roomIterator.hasNext()) {
roomIterator.next();
roomIterator.value()->gamesMutex.unlock();
roomIterator.value()->gamesLock.unlock();
roomIterator.value()->usersLock.unlock();
}
server->roomsLock.unlock();

View file

@ -127,6 +127,7 @@ int main(int argc, char *argv[])
QSettings *settings = new QSettings("servatrice.ini", QSettings::IniFormat);
loggerThread = new QThread;
loggerThread->setObjectName("logger");
logger = new ServerLogger(logToConsole);
logger->moveToThread(loggerThread);
@ -151,12 +152,16 @@ int main(int argc, char *argv[])
sigemptyset(&segv.sa_mask);
sigaction(SIGSEGV, &segv, 0);
sigaction(SIGABRT, &segv, 0);
signal(SIGPIPE, SIG_IGN);
#endif
rng = new RNG_SFMT;
std::cerr << "Servatrice " << VERSION_STRING << " starting." << std::endl;
std::cerr << "-------------------------" << std::endl;
PasswordHasher::initialize();
if (testRandom)
testRNG();
if (testHashFunction)

View file

@ -3,6 +3,14 @@
#include <string.h>
#include <gcrypt.h>
void PasswordHasher::initialize()
{
// These calls are required by libgcrypt before we use any of its functions.
gcry_check_version(0);
gcry_control(GCRYCTL_DISABLE_SECMEM, 0);
gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
}
QString PasswordHasher::computeHash(const QString &password, const QString &salt)
{
const int algo = GCRY_MD_SHA512;

View file

@ -5,6 +5,7 @@
class PasswordHasher {
public:
static void initialize();
static QString computeHash(const QString &password, const QString &salt);
};

View file

@ -33,8 +33,6 @@
#include "server_logger.h"
#include "main.h"
#include "decklist.h"
#include "pb/game_replay.pb.h"
#include "pb/event_replay_added.pb.h"
#include "pb/event_server_message.pb.h"
#include "pb/event_server_shutdown.pb.h"
#include "pb/event_connection_closed.pb.h"
@ -44,6 +42,7 @@ Servatrice_GameServer::Servatrice_GameServer(Servatrice *_server, int _numberPoo
server(_server)
{
if (_numberPools == 0) {
server->setThreaded(false);
Servatrice_DatabaseInterface *newDatabaseInterface = new Servatrice_DatabaseInterface(0, server);
Servatrice_ConnectionPool *newPool = new Servatrice_ConnectionPool(newDatabaseInterface);
@ -57,6 +56,7 @@ Servatrice_GameServer::Servatrice_GameServer(Servatrice *_server, int _numberPoo
Servatrice_ConnectionPool *newPool = new Servatrice_ConnectionPool(newDatabaseInterface);
QThread *newThread = new QThread;
newThread->setObjectName("pool_" + QString::number(i));
newPool->moveToThread(newThread);
newDatabaseInterface->moveToThread(newThread);
server->addDatabaseInterface(newThread, newDatabaseInterface);
@ -253,6 +253,7 @@ bool Servatrice::initServer()
}
QThread *thread = new QThread;
thread->setObjectName("isl_" + QString::number(prop.id));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
IslInterface *interface = new IslInterface(prop.id, prop.hostname, prop.address.toString(), prop.controlPort, prop.cert, cert, key, this);
@ -290,6 +291,7 @@ bool Servatrice::initServer()
const int numberPools = settings->value("server/number_pools", 1).toInt();
gameServer = new Servatrice_GameServer(this, numberPools, servatriceDatabaseInterface->getDatabase(), this);
gameServer->setMaxPendingConnections(1000);
const int gamePort = settings->value("server/port", 4747).toInt();
qDebug() << "Starting server on port" << gamePort;
if (gameServer->listen(QHostAddress::Any, gamePort))
@ -354,55 +356,6 @@ QList<ServerSocketInterface *> Servatrice::getUsersWithAddressAsList(const QHost
return result;
}
void Servatrice::storeGameInformation(int secondsElapsed, const QSet<QString> &allPlayersEver, const QSet<QString> &allSpectatorsEver, const QList<GameReplay *> &replayList)
{
const ServerInfo_Game &gameInfo = replayList.first()->game_info();
Server_Room *room = rooms.value(gameInfo.room_id());
Event_ReplayAdded replayEvent;
ServerInfo_ReplayMatch *replayMatchInfo = replayEvent.mutable_match_info();
replayMatchInfo->set_game_id(gameInfo.game_id());
replayMatchInfo->set_room_name(room->getName().toStdString());
replayMatchInfo->set_time_started(QDateTime::currentDateTime().addSecs(-secondsElapsed).toTime_t());
replayMatchInfo->set_length(secondsElapsed);
replayMatchInfo->set_game_name(gameInfo.description());
const QStringList &allGameTypes = room->getGameTypes();
QStringList gameTypes;
for (int i = gameInfo.game_types_size() - 1; i >= 0; --i)
gameTypes.append(allGameTypes[gameInfo.game_types(i)]);
QSetIterator<QString> playerIterator(allPlayersEver);
while (playerIterator.hasNext())
replayMatchInfo->add_player_names(playerIterator.next().toStdString());
for (int i = 0; i < replayList.size(); ++i) {
ServerInfo_Replay *replayInfo = replayMatchInfo->add_replay_list();
replayInfo->set_replay_id(replayList[i]->replay_id());
replayInfo->set_replay_name(gameInfo.description());
replayInfo->set_duration(replayList[i]->duration_seconds());
}
QSet<QString> allUsersInGame = allPlayersEver + allSpectatorsEver;
QSetIterator<QString> allUsersIterator(allUsersInGame);
SessionEvent *sessionEvent = Server_ProtocolHandler::prepareSessionEvent(replayEvent);
clientsLock.lockForRead();
while (allUsersIterator.hasNext()) {
const QString userName = allUsersIterator.next();
Server_AbstractUserInterface *userHandler = users.value(userName);
if (!userHandler)
userHandler = externalUsers.value(userName);
if (userHandler)
userHandler->sendProtocolItem(*sessionEvent);
}
clientsLock.unlock();
delete sessionEvent;
getDatabaseInterface()->storeGameInformation(room->getName(), gameTypes, gameInfo, allPlayersEver, allSpectatorsEver, replayList);
}
void Servatrice::updateLoginMessage()
{
if (!servatriceDatabaseInterface->checkSql())

View file

@ -122,6 +122,7 @@ private:
QMap<int, IslInterface *> islInterfaces;
public slots:
void scheduleShutdown(const QString &reason, int minutes);
void updateLoginMessage();
public:
Servatrice(QSettings *_settings, QObject *parent = 0);
~Servatrice();
@ -139,12 +140,10 @@ public:
AuthenticationMethod getAuthenticationMethod() const { return authenticationMethod; }
QString getDbPrefix() const { return dbPrefix; }
int getServerId() const { return serverId; }
void updateLoginMessage();
int getUsersWithAddress(const QHostAddress &address) const;
QList<ServerSocketInterface *> getUsersWithAddressAsList(const QHostAddress &address) const;
void incTxBytes(quint64 num);
void incRxBytes(quint64 num);
void storeGameInformation(int secondsElapsed, const QSet<QString> &allPlayersEver, const QSet<QString> &allSpectatorsEver, const QList<GameReplay *> &replays);
void addDatabaseInterface(QThread *thread, Servatrice_DatabaseInterface *databaseInterface);
bool islConnectionExists(int serverId) const;

View file

@ -507,15 +507,15 @@ void Servatrice_DatabaseInterface::storeGameInformation(const QString &roomName,
}
}
DeckList *Servatrice_DatabaseInterface::getDeckFromDatabase(int deckId, const QString &userName)
DeckList *Servatrice_DatabaseInterface::getDeckFromDatabase(int deckId, int userId)
{
checkSql();
QSqlQuery query(sqlDatabase);
query.prepare("select content from " + server->getDbPrefix() + "_decklist_files where id = :id and user = :user");
query.prepare("select content from " + server->getDbPrefix() + "_decklist_files where id = :id and id_user = :id_user");
query.bindValue(":id", deckId);
query.bindValue(":user", userName);
query.bindValue(":id_user", userId);
execSqlQuery(query);
if (!query.next())
throw Response::RespNameNotFound;

View file

@ -38,7 +38,7 @@ public:
bool isInIgnoreList(const QString &whoseList, const QString &who);
ServerInfo_User getUserData(const QString &name, bool withId = false);
void storeGameInformation(const QString &roomName, const QStringList &roomGameTypes, const ServerInfo_Game &gameInfo, const QSet<QString> &allPlayersEver, const QSet<QString> &allSpectatorsEver, const QList<GameReplay *> &replayList);
DeckList *getDeckFromDatabase(int deckId, const QString &userName);
DeckList *getDeckFromDatabase(int deckId, int userId);
int getNextGameId();
int getNextReplayId();

View file

@ -72,18 +72,25 @@ ServerSocketInterface::ServerSocketInterface(Servatrice *_server, Servatrice_Dat
socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
connect(socket, SIGNAL(readyRead()), this, SLOT(readClient()));
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(catchSocketError(QAbstractSocket::SocketError)));
connect(this, SIGNAL(outputBufferChanged()), this, SLOT(flushOutputBuffer()), Qt::QueuedConnection);
// Never call flushOutputQueue directly from outputQueueChanged. In case of a socket error,
// it could lead to this object being destroyed while another function is still on the call stack. -> mutex deadlocks etc.
connect(this, SIGNAL(outputQueueChanged()), this, SLOT(flushOutputQueue()), Qt::QueuedConnection);
}
ServerSocketInterface::~ServerSocketInterface()
{
logger->logMessage("ServerSocketInterface destructor", this);
flushOutputBuffer();
flushOutputQueue();
}
void ServerSocketInterface::initConnection(int socketDescriptor)
{
// Add this object to the server's list of connections before it can receive socket events.
// Otherwise, in case a of a socket error, it could be removed from the list before it is added.
server->addClient(this);
socket->setSocketDescriptor(socketDescriptor);
logger->logMessage(QString("Incoming connection: %1").arg(socket->peerAddress().toString()), this);
initSessionDeprecated();
@ -93,11 +100,10 @@ void ServerSocketInterface::initSessionDeprecated()
{
// dirty hack to make v13 client display the correct error message
outputBufferMutex.lock();
outputBuffer = "<?xml version=\"1.0\"?><cockatrice_server_stream version=\"14\">";
outputBufferMutex.unlock();
emit outputBufferChanged();
QByteArray buf;
buf.append("<?xml version=\"1.0\"?><cockatrice_server_stream version=\"14\">");
socket->write(buf);
socket->flush();
}
bool ServerSocketInterface::initSession()
@ -121,21 +127,9 @@ bool ServerSocketInterface::initSession()
return false;
}
server->addClient(this);
return true;
}
void ServerSocketInterface::flushOutputBuffer()
{
QMutexLocker locker(&outputBufferMutex);
if (outputBuffer.isEmpty())
return;
servatrice->incTxBytes(outputBuffer.size());
socket->write(outputBuffer);
socket->flush();
outputBuffer.clear();
}
void ServerSocketInterface::readClient()
{
QByteArray data = socket->readAll();
@ -183,18 +177,42 @@ void ServerSocketInterface::catchSocketError(QAbstractSocket::SocketError socket
void ServerSocketInterface::transmitProtocolItem(const ServerMessage &item)
{
QByteArray buf;
unsigned int size = item.ByteSize();
buf.resize(size + 4);
item.SerializeToArray(buf.data() + 4, size);
buf.data()[3] = (unsigned char) size;
buf.data()[2] = (unsigned char) (size >> 8);
buf.data()[1] = (unsigned char) (size >> 16);
buf.data()[0] = (unsigned char) (size >> 24);
outputQueueMutex.lock();
outputQueue.append(item);
outputQueueMutex.unlock();
QMutexLocker locker(&outputBufferMutex);
outputBuffer.append(buf);
emit outputBufferChanged();
emit outputQueueChanged();
}
void ServerSocketInterface::flushOutputQueue()
{
QMutexLocker locker(&outputQueueMutex);
if (outputQueue.isEmpty())
return;
int totalBytes = 0;
while (!outputQueue.isEmpty()) {
ServerMessage item = outputQueue.takeFirst();
locker.unlock();
QByteArray buf;
unsigned int size = item.ByteSize();
buf.resize(size + 4);
item.SerializeToArray(buf.data() + 4, size);
buf.data()[3] = (unsigned char) size;
buf.data()[2] = (unsigned char) (size >> 8);
buf.data()[1] = (unsigned char) (size >> 16);
buf.data()[0] = (unsigned char) (size >> 24);
// In case socket->write() calls catchSocketError(), the mutex must not be locked during this call.
socket->write(buf);
totalBytes += size + 4;
locker.relock();
}
locker.unlock();
servatrice->incTxBytes(totalBytes);
// see above wrt mutex
socket->flush();
}
void ServerSocketInterface::logDebugMessage(const QString &message)
@ -324,10 +342,10 @@ int ServerSocketInterface::getDeckPathId(int basePathId, QStringList path)
return 0;
QSqlQuery query(sqlInterface->getDatabase());
query.prepare("select id from " + servatrice->getDbPrefix() + "_decklist_folders where id_parent = :id_parent and name = :name and user = :user");
query.prepare("select id from " + servatrice->getDbPrefix() + "_decklist_folders where id_parent = :id_parent and name = :name and id_user = :id_user");
query.bindValue(":id_parent", basePathId);
query.bindValue(":name", path.takeFirst());
query.bindValue(":user", QString::fromStdString(userInfo->name()));
query.bindValue(":id_user", userInfo->id());
if (!sqlInterface->execSqlQuery(query))
return -1;
if (!query.next())
@ -347,9 +365,9 @@ int ServerSocketInterface::getDeckPathId(const QString &path)
bool ServerSocketInterface::deckListHelper(int folderId, ServerInfo_DeckStorage_Folder *folder)
{
QSqlQuery query(sqlInterface->getDatabase());
query.prepare("select id, name from " + servatrice->getDbPrefix() + "_decklist_folders where id_parent = :id_parent and user = :user");
query.prepare("select id, name from " + servatrice->getDbPrefix() + "_decklist_folders where id_parent = :id_parent and id_user = :id_user");
query.bindValue(":id_parent", folderId);
query.bindValue(":user", QString::fromStdString(userInfo->name()));
query.bindValue(":id_user", userInfo->id());
if (!sqlInterface->execSqlQuery(query))
return false;
@ -362,9 +380,9 @@ bool ServerSocketInterface::deckListHelper(int folderId, ServerInfo_DeckStorage_
return false;
}
query.prepare("select id, name, upload_time from " + servatrice->getDbPrefix() + "_decklist_files where id_folder = :id_folder and user = :user");
query.prepare("select id, name, upload_time from " + servatrice->getDbPrefix() + "_decklist_files where id_folder = :id_folder and id_user = :id_user");
query.bindValue(":id_folder", folderId);
query.bindValue(":user", QString::fromStdString(userInfo->name()));
query.bindValue(":id_user", userInfo->id());
if (!sqlInterface->execSqlQuery(query))
return false;
@ -412,9 +430,9 @@ Response::ResponseCode ServerSocketInterface::cmdDeckNewDir(const Command_DeckNe
return Response::RespNameNotFound;
QSqlQuery query(sqlInterface->getDatabase());
query.prepare("insert into " + servatrice->getDbPrefix() + "_decklist_folders (id_parent, user, name) values(:id_parent, :user, :name)");
query.prepare("insert into " + servatrice->getDbPrefix() + "_decklist_folders (id_parent, id_user, name) values(:id_parent, :id_user, :name)");
query.bindValue(":id_parent", folderId);
query.bindValue(":user", QString::fromStdString(userInfo->name()));
query.bindValue(":id_user", userInfo->id());
query.bindValue(":name", QString::fromStdString(cmd.dir_name()));
if (!sqlInterface->execSqlQuery(query))
return Response::RespContextError;
@ -463,9 +481,9 @@ Response::ResponseCode ServerSocketInterface::cmdDeckDel(const Command_DeckDel &
sqlInterface->checkSql();
QSqlQuery query(sqlInterface->getDatabase());
query.prepare("select id from " + servatrice->getDbPrefix() + "_decklist_files where id = :id and user = :user");
query.prepare("select id from " + servatrice->getDbPrefix() + "_decklist_files where id = :id and id_user = :id_user");
query.bindValue(":id", cmd.deck_id());
query.bindValue(":user", QString::fromStdString(userInfo->name()));
query.bindValue(":id_user", userInfo->id());
sqlInterface->execSqlQuery(query);
if (!query.next())
return Response::RespNameNotFound;
@ -500,9 +518,9 @@ Response::ResponseCode ServerSocketInterface::cmdDeckUpload(const Command_DeckUp
return Response::RespNameNotFound;
QSqlQuery query(sqlInterface->getDatabase());
query.prepare("insert into " + servatrice->getDbPrefix() + "_decklist_files (id_folder, user, name, upload_time, content) values(:id_folder, :user, :name, NOW(), :content)");
query.prepare("insert into " + servatrice->getDbPrefix() + "_decklist_files (id_folder, id_user, name, upload_time, content) values(:id_folder, :id_user, :name, NOW(), :content)");
query.bindValue(":id_folder", folderId);
query.bindValue(":user", QString::fromStdString(userInfo->name()));
query.bindValue(":id_user", userInfo->id());
query.bindValue(":name", deckName);
query.bindValue(":content", deckStr);
sqlInterface->execSqlQuery(query);
@ -515,9 +533,9 @@ Response::ResponseCode ServerSocketInterface::cmdDeckUpload(const Command_DeckUp
rc.setResponseExtension(re);
} else if (cmd.has_deck_id()) {
QSqlQuery query(sqlInterface->getDatabase());
query.prepare("update " + servatrice->getDbPrefix() + "_decklist_files set name=:name, upload_time=NOW(), content=:content where id = :id_deck and user = :user");
query.prepare("update " + servatrice->getDbPrefix() + "_decklist_files set name=:name, upload_time=NOW(), content=:content where id = :id_deck and id_user = :id_user");
query.bindValue(":id_deck", cmd.deck_id());
query.bindValue(":user", QString::fromStdString(userInfo->name()));
query.bindValue(":id_user", userInfo->id());
query.bindValue(":name", deckName);
query.bindValue(":content", deckStr);
sqlInterface->execSqlQuery(query);
@ -544,7 +562,7 @@ Response::ResponseCode ServerSocketInterface::cmdDeckDownload(const Command_Deck
DeckList *deck;
try {
deck = sqlInterface->getDeckFromDatabase(cmd.deck_id(), QString::fromStdString(userInfo->name()));
deck = sqlInterface->getDeckFromDatabase(cmd.deck_id(), userInfo->id());
} catch(Response::ResponseCode r) {
return r;
}
@ -701,6 +719,7 @@ Response::ResponseCode ServerSocketInterface::cmdBanFromServer(const Command_Ban
query.bindValue(":visible_reason", QString::fromStdString(cmd.visible_reason()));
sqlInterface->execSqlQuery(query);
servatrice->clientsLock.lockForRead();
QList<ServerSocketInterface *> userList = servatrice->getUsersWithAddressAsList(QHostAddress(address));
ServerSocketInterface *user = static_cast<ServerSocketInterface *>(server->getUsers().value(userName));
if (user && !userList.contains(user))
@ -715,10 +734,11 @@ Response::ResponseCode ServerSocketInterface::cmdBanFromServer(const Command_Ban
for (int i = 0; i < userList.size(); ++i) {
SessionEvent *se = userList[i]->prepareSessionEvent(event);
userList[i]->sendProtocolItem(*se);
userList[i]->prepareDestroy();
delete se;
QMetaObject::invokeMethod(userList[i], "prepareDestroy", Qt::QueuedConnection);
}
}
servatrice->clientsLock.unlock();
return Response::RespOk;
}
@ -728,7 +748,7 @@ Response::ResponseCode ServerSocketInterface::cmdBanFromServer(const Command_Ban
Response::ResponseCode ServerSocketInterface::cmdUpdateServerMessage(const Command_UpdateServerMessage & /*cmd*/, ResponseContainer & /*rc*/)
{
servatrice->updateLoginMessage();
QMetaObject::invokeMethod(server, "updateLoginMessage");
return Response::RespOk;
}

View file

@ -54,18 +54,19 @@ class ServerSocketInterface : public Server_ProtocolHandler
private slots:
void readClient();
void catchSocketError(QAbstractSocket::SocketError socketError);
void flushOutputBuffer();
void flushOutputQueue();
signals:
void outputBufferChanged();
void outputQueueChanged();
protected:
void logDebugMessage(const QString &message);
private:
QMutex outputBufferMutex;
QMutex outputQueueMutex;
Servatrice *servatrice;
Servatrice_DatabaseInterface *sqlInterface;
QTcpSocket *socket;
QByteArray inputBuffer, outputBuffer;
QByteArray inputBuffer;
QList<ServerMessage> outputQueue;
bool messageInProgress;
bool handshakeStarted;
int messageLength;