diff options
Diffstat (limited to 'src/TcpTasks')
21 files changed, 839 insertions, 0 deletions
diff --git a/src/TcpTasks/ListenClientHandler/compileserverlist.cpp b/src/TcpTasks/ListenClientHandler/compileserverlist.cpp new file mode 100644 index 0000000..5423fbf --- /dev/null +++ b/src/TcpTasks/ListenClientHandler/compileserverlist.cpp @@ -0,0 +1,39 @@ +#include "listenclienthandler.h" + +QByteArray ListenClientHandler::compileServerlist (const QString &gamename, + const int &serverAge_s, + const bool &cmp) +{ + // retrieve servers from database (both direct and sync) + QSqlQuery q = selectServerList( gamename, serverAge_s, true); + + // output array + QByteArray compiledList; + QDataStream dsList(&compiledList,QIODevice::WriteOnly); + + // iterate through resulting queries and add to compile list + while (q.next()) + { + QString ip = q.value(0).toString(); + unsigned short port = q.value(1).value<unsigned short>(); + + // add server to list (compressed/cmp or plaintext) + if (cmp) + { + // QHostAddress.toIPv4Address() provides the correct ABCD format, append EF port bytes + dsList << QHostAddress(ip).toIPv4Address() << port; + } + else // plaintext output + { + // ip in database is plaintext already + compiledList += QStringLiteral("\\ip\\%1:%2").arg(ip, QString::number(port)); + } + + } // while next + + // terminator after list + compiledList.append("\\final\\"); + + // list compiled + return compiledList; +} diff --git a/src/TcpTasks/ListenClientHandler/compilesynclist.cpp b/src/TcpTasks/ListenClientHandler/compilesynclist.cpp new file mode 100644 index 0000000..4d333b3 --- /dev/null +++ b/src/TcpTasks/ListenClientHandler/compilesynclist.cpp @@ -0,0 +1,35 @@ +#include "listenclienthandler.h" + +QByteArray ListenClientHandler::compileSyncList(const QStringList &gamenameList, + const int &serverAge_s) +{ + // output list in \\gamename1\\ip:port ip:port\\gamename2\\ip:port ip:port\\final\\ format + QByteArray compiledList; + + // go through list of gamenames + QStringListIterator gamenameListNames(gamenameList); + while ( gamenameListNames.hasNext() ) + { + // retrieve servers from database (only verified, not from other syncs) + QString gamename = gamenameListNames.next(); + QSqlQuery q = selectServerList( gamename, serverAge_s, false); + + // identifier + compiledList += QStringLiteral("\\%1\\").arg(gamename); + + // iterate through resulting queries and add to compile list + // ip-addresses are stored as text and do not need to be converted back and forth + while (q.next()) + { + // add to list -> ip:port + compiledList += QStringLiteral("%1:%2 ").arg(q.value(0).toString(), q.value(1).toString()); + + } // while next + } + + // terminator after list + compiledList.append("\\final\\"); + + // list compiled + return compiledList; +} diff --git a/src/TcpTasks/ListenClientHandler/listenclientdisconnect.cpp b/src/TcpTasks/ListenClientHandler/listenclientdisconnect.cpp new file mode 100644 index 0000000..55e5f9f --- /dev/null +++ b/src/TcpTasks/ListenClientHandler/listenclientdisconnect.cpp @@ -0,0 +1,21 @@ +#include "listenclienthandler.h" + +void ListenClientHandler::disconnect() +{ + _timeOut.stop(); + _tcpSocket->disconnectFromHost(); +} + +void ListenClientHandler::onListenClientDisconnect() +{ + _timeOut.stop(); + _coreObject->Log.logEvent("tcp", QStringLiteral("%1 disconnected").arg(_clientLabel) ); + this->deleteLater(); +} + +void ListenClientHandler::onListenClientTimeOut() +{ + _timeOut.stop(); + _coreObject->Log.logEvent("tcp", QStringLiteral("%1 timed out").arg(_clientLabel) ); + this->disconnect(); +} diff --git a/src/TcpTasks/ListenClientHandler/listenclienthandler.cpp b/src/TcpTasks/ListenClientHandler/listenclienthandler.cpp new file mode 100644 index 0000000..bc0afb4 --- /dev/null +++ b/src/TcpTasks/ListenClientHandler/listenclienthandler.cpp @@ -0,0 +1,28 @@ +#include "listenclienthandler.h" + +ListenClientHandler::ListenClientHandler(const QSharedPointer<CoreObject> &coreObject, + QTcpSocket *tcpSocket) + : _tcpSocket(tcpSocket) +{ + // create local access + this->_coreObject = coreObject; + + // connect read, timeout and disconnect events + connect(tcpSocket, &QTcpSocket::readyRead, this, &ListenClientHandler::onListenClientRead); + connect(tcpSocket, &QTcpSocket::disconnected, this, &ListenClientHandler::onListenClientDisconnect); + connect(&_timeOut, &QTimer::timeout, this, &ListenClientHandler::onListenClientTimeOut); + + // timeout + _timeOut.setInterval( _timeOutTime_ms ); + _timeOut.start(); + + // challenge + _clientLabel = QStringLiteral("%1:%2").arg(tcpSocket->peerAddress().toString(), QString::number(tcpSocket->peerPort())); + _secure = genChallengeString(6, false); + + _tcpSocket->write( QStringLiteral("\\basic\\\\secure\\%1").arg(_secure).toUtf8() ); + _tcpSocket->flush(); + + // log new connections + _coreObject->Log.logEvent("tcp", QStringLiteral("%1 connected").arg(_clientLabel) ); +} diff --git a/src/TcpTasks/ListenClientHandler/listenclienthandler.h b/src/TcpTasks/ListenClientHandler/listenclienthandler.h new file mode 100644 index 0000000..f7c300b --- /dev/null +++ b/src/TcpTasks/ListenClientHandler/listenclienthandler.h @@ -0,0 +1,49 @@ +#ifndef LISTENCLIENTHANDLER_H +#define LISTENCLIENTHANDLER_H + +#include <QTimer> +#include <QTcpSocket> +#include <QHostAddress> + +#include "Core/CoreObject/coreobject.h" +#include "Database/Common/commonactions.h" + +#include "Protocols/GameSpy0/gamespy0.h" +#include "Protocols/GameSpy0/securevalidate.h" + +class ListenClientHandler : public QObject +{ + Q_OBJECT +public: + ListenClientHandler(const QSharedPointer<CoreObject> &coreObject, + QTcpSocket *tcpSocket); + +private: + const int _timeOutTime_ms = 7500; + + QSharedPointer<CoreObject> _coreObject; + QScopedPointer<QTcpSocket> _tcpSocket; + QTimer _timeOut; + QString _rxBuffer = ""; + bool _hasValidated = false; + + QString _clientLabel; + QString _secure; +private: + void disconnect(); + + // database functions + QByteArray compileServerlist (const QString &gamename, + const int &serverAge_s, + const bool &cmp); + QByteArray compileSyncList(const QStringList &gamenameList, + const int &serverAge_s); + +private slots: + void onListenClientRead(); + void onListenClientTimeOut(); + void onListenClientDisconnect(); + +}; + +#endif // LISTENCLIENTHANDLER_H diff --git a/src/TcpTasks/ListenClientHandler/onlistenclientread.cpp b/src/TcpTasks/ListenClientHandler/onlistenclientread.cpp new file mode 100644 index 0000000..83cc822 --- /dev/null +++ b/src/TcpTasks/ListenClientHandler/onlistenclientread.cpp @@ -0,0 +1,185 @@ +#include "listenclienthandler.h" + +void ListenClientHandler::onListenClientRead() +{ + // clear buffer from previous intereactions + if ( _rxBuffer.contains("\\final\\") ) + { + // if the keyword "\\final\\" is already found, a previous interaction was completed + // it is now safe to clear the buffer + _rxBuffer = ""; + } + + // read incoming data + QByteArray receiveBuffer = _tcpSocket->readAll(); + _coreObject->Log.logEvent("tcp", QStringLiteral("%1 sent '%2'") + .arg(_clientLabel, receiveBuffer.data()) ); + + // reset timeout when data is received + _timeOut.start(); + + // add data to buffer and wait for the interaction to be complete + _rxBuffer += receiveBuffer; + if ( ! receiveBuffer.contains("\\final\\") ) + { + // wait for data to be complete + return; + } + + // process data according to \key\value formatting + QMultiHash<QString, QString> receiveData = parseGameSpy0Buffer(_rxBuffer.toLatin1()); + + // part 1: validation challenge. + if ( receiveData.contains("validate") ) + { + // combined queries provide two gamenames: one for authentication and one for list request. + QString gamename = ( receiveData.values("gamename").size() >= 2 + ? receiveData.values("gamename").takeLast() // removes duplicate gamename + : receiveData.value("gamename","") ); + + // sanity check + if ( _coreObject->SupportedGames.contains(gamename) ) + { + // get response + AuthResult authResult = validateGamename(false, // tcp client, not a beacon + gamename, + receiveData.value("validate",""), + _coreObject->SupportedGames.value(gamename).cipher, + _secure, + receiveData.value("enctype", "0").toInt() ); + + // authenticate + if ( authResult.auth ) + { + _hasValidated = true; + _coreObject->Log.logEvent("secure", QStringLiteral("%1 passed validation for %2").arg(_clientLabel, gamename)); + } + else + { + _coreObject->Log.logEvent("secure", QStringLiteral("%1 failed validation for %2").arg(_clientLabel, gamename)); + + // log detailed information on failure + _coreObject->Log.logEvent("secure", QStringLiteral("secure: '%1', gamename: '%2', validate: '%3', expected: '%4'") + .arg(_secure, gamename, receiveData.value("validate", "null"), authResult.validate )); + + // kick client after unsuccessful validation + this->disconnect(); + } + } + else + { + _coreObject->Log.logEvent("secure", QStringLiteral("%1 tried to validate for unknown game %2").arg(_clientLabel, gamename)); + + // log detailed information on failure + _coreObject->Log.logEvent("secure", QStringLiteral("secure: '%1', gamename: '%2', validate: '%3'") + .arg(_secure, gamename, receiveData.value("validate", "NULL") )); + // kick client after unsuccessful validation + this->disconnect(); + } + + } // hasValidated ? + + // part 2: after validation, send serverlist + if ( receiveData.contains("list") ) + { + // list request is only granted after validation + if ( ! _hasValidated ) + { + // kick client after unsuccessful validation + _coreObject->Log.logEvent("secure", QStringLiteral("%1 requested a list for %2 without validating").arg(_clientLabel, receiveData.value("gamename", "")) ); + this->disconnect(); + return; + } + + // get list from db and send it + QByteArray writeBuffer = compileServerlist( + receiveData.value("gamename", ""), + _coreObject->Settings.ListenServerSettings.serverttl_s, + (receiveData.value("list","").compare("cmp", Qt::CaseInsensitive) == 0 )); + _tcpSocket->write(writeBuffer); + _coreObject->Log.logEvent("list", QStringLiteral("%1 received the list for %2").arg(_clientLabel, receiveData.value("gamename", ""))); + + // all done + this->disconnect(); + + } // list + + // part 2b: 333networks synchronisation + if ( receiveData.contains("sync") ) + { + // list request is only granted after validation + if ( ! _hasValidated ) + { + // kick client after unsuccessful validation + _coreObject->Log.logEvent("secure", QStringLiteral("%1 requested to sync for %2 without validating").arg(_clientLabel, receiveData.value("gamename", "")) ); + this->disconnect(); + return; + } + + // do not sync with ourself + QString remoteIdentity = receiveData.value("msid", ""); + if ( _coreObject->masterserverIdentity == remoteIdentity ) + { + // msid match -- skip syncing + _tcpSocket->write("\\final\\"); + _coreObject->Log.logEvent("list", QStringLiteral("skipping sync for self at %1").arg(_clientLabel) ); + this->disconnect(); + return; + } + + // sync request has options "all" or "gamename_1 gamename_2 gamename_n" + QStringList gamenameList; + if ( receiveData.value("sync","").toLower().compare("all") == 0 ) + { + // get list of gamenames from database + gamenameList = getGamenames(_coreObject->Settings.ListenServerSettings.serverttl_s); + } + else + { + // only load gamenames that are in our list of available games + QStringListIterator rawListNames( receiveData.value("sync","").toLower().split(" ") ); + while ( rawListNames.hasNext() ) + { + QString rawGamename = overrideGamename( rawListNames.next() ); + if ( _coreObject->SupportedGames.contains(rawGamename) ) + { + gamenameList.append(rawGamename); + } + } + } + + // get compiled sync list for all requested gamenames + QByteArray writeBuffer = compileSyncList( + gamenameList, + _coreObject->Settings.ListenServerSettings.serverttl_s); + _tcpSocket->write(writeBuffer); + + // log (displays all the gamenames that were synced or empty when no gamenames/servers were available) + _coreObject->Log.logEvent("list", QStringLiteral("%1 received the sync list for %2").arg(_clientLabel, gamenameList.join(",")) ); + + // all done + this->disconnect(); + + } // sync + + // optional: echo + if ( receiveData.contains("echo") ) + { + _coreObject->Log.logEvent("echo", QStringLiteral("%1 echoed %2").arg(_clientLabel, receiveData.value("echo", "")) ); + _tcpSocket->write("\\echo_reply\\" + receiveData.value("echo", "").toLatin1() ); + } + + // else + + // unknown, log as error + if ( ! receiveData.contains("validate") and + ! receiveData.contains("list") and + ! receiveData.contains("sync") and + ! receiveData.contains("echo") ) + { + _coreObject->Log.logEvent("unknown", QStringLiteral("%1 with unknown request %2").arg(_clientLabel, _rxBuffer ) ); + this->disconnect(); + } + + +} diff --git a/src/TcpTasks/ListenServer/listenserver.cpp b/src/TcpTasks/ListenServer/listenserver.cpp new file mode 100644 index 0000000..1269f21 --- /dev/null +++ b/src/TcpTasks/ListenServer/listenserver.cpp @@ -0,0 +1,7 @@ +#include "listenserver.h" + +ListenServer::ListenServer(const QSharedPointer<CoreObject> &coreObject) +{ + // create local access + this->_coreObject = coreObject; +} diff --git a/src/TcpTasks/ListenServer/listenserver.h b/src/TcpTasks/ListenServer/listenserver.h new file mode 100644 index 0000000..bac0d66 --- /dev/null +++ b/src/TcpTasks/ListenServer/listenserver.h @@ -0,0 +1,31 @@ +#ifndef LISTENSERVER_H +#define LISTENSERVER_H + +#include <QTimer> +#include <QTcpServer> +#include <QTcpSocket> + +#include "Core/CoreObject/coreobject.h" +#include "TcpTasks/ListenClientHandler/listenclienthandler.h" + +class ListenServer: public QObject +{ + Q_OBJECT +public: + ListenServer(const QSharedPointer<CoreObject> &coreObject); + bool listen(); + +private: + QSharedPointer<CoreObject> _coreObject; + + // tcp server socket + QTcpServer _tcpServer; + +signals: + +private slots: + void onListenConnection(); + +}; + +#endif // LISTENSERVER_H diff --git a/src/TcpTasks/ListenServer/onlistenconnection.cpp b/src/TcpTasks/ListenServer/onlistenconnection.cpp new file mode 100644 index 0000000..12d830a --- /dev/null +++ b/src/TcpTasks/ListenServer/onlistenconnection.cpp @@ -0,0 +1,7 @@ +#include "listenserver.h" + +void ListenServer::onListenConnection() +{ + // handle client in its own class + new ListenClientHandler(_coreObject, _tcpServer.nextPendingConnection()); +} diff --git a/src/TcpTasks/ListenServer/tcplisten.cpp b/src/TcpTasks/ListenServer/tcplisten.cpp new file mode 100644 index 0000000..309adbc --- /dev/null +++ b/src/TcpTasks/ListenServer/tcplisten.cpp @@ -0,0 +1,25 @@ +#include "listenserver.h" + +bool ListenServer::listen() +{ + // connect socket + connect(&_tcpServer, &QTcpServer::newConnection, this, &ListenServer::onListenConnection); + + // start listening for new TCP connections + bool error = _tcpServer.listen(QHostAddress::Any, _coreObject->Settings.ListenServerSettings.listenPort); + + // error notification: + if ( ! error ) + { + // complete startup + _coreObject->Log.logEvent("fatal", QStringLiteral("error starting tcp Listen server: %1") + .arg(_tcpServer.errorString())); + return false; + } + + // complete startup + _coreObject->Log.logEvent("info", QStringLiteral("start listening for TCP beacons on port %1") + .arg(_coreObject->Settings.ListenServerSettings.listenPort)); + + return true; +} diff --git a/src/TcpTasks/SyncClient/onsyncconnect.cpp b/src/TcpTasks/SyncClient/onsyncconnect.cpp new file mode 100644 index 0000000..3e05864 --- /dev/null +++ b/src/TcpTasks/SyncClient/onsyncconnect.cpp @@ -0,0 +1,10 @@ +#include "syncclient.h" + +void SyncClient::onSyncConnect() +{ + // reset timeout + _timeOut.start(); + + // log + _coreObject->Log.logEvent("tcp", QStringLiteral("connected to %1").arg(_clientLabel) ); +} diff --git a/src/TcpTasks/SyncClient/onsyncdisconnect.cpp b/src/TcpTasks/SyncClient/onsyncdisconnect.cpp new file mode 100644 index 0000000..4bc8eac --- /dev/null +++ b/src/TcpTasks/SyncClient/onsyncdisconnect.cpp @@ -0,0 +1,49 @@ +#include "syncclient.h" + +void SyncClient::onSyncDisconnect() +{ + // remote host closed the connection (typically occurs after receiving the list from remote host) + if (_tcpSocket.error() == QAbstractSocket::RemoteHostClosedError ) + { + _coreObject->Log.logEvent("tcp", QStringLiteral("disconnected from %1").arg(_clientLabel) ); + } + + // timer already stopped or a timeout occurred + if ( ! _timeOut.isActive() or _tcpSocket.error() == QAbstractSocket::SocketTimeoutError) + { + _coreObject->Log.logEvent("warning", QStringLiteral("timeout while attempting to sync with %1 (1)").arg(_clientLabel)); + } + + // an error occured and is reported, excluding... + if ( _tcpSocket.error() != QAbstractSocket::RemoteHostClosedError and // ...regular disconnect caught above + _tcpSocket.error() != QAbstractSocket::SocketTimeoutError and // ...timeout caught above + _tcpSocket.error() != QAbstractSocket::UnknownSocketError ) // ...QTimer timeout does not leave an errorcode (defaults to unknown error) + { + _coreObject->Log.logEvent("warning", QStringLiteral("error while syncing with %1: %2").arg(_clientLabel, _tcpSocket.errorString())); + } + + // stop timer if necessary and delete this client + _timeOut.stop(); + this->deleteLater(); +} + +void SyncClient::onSyncTimeOut() +{ + // if no error was specified while timer expired, there was a connection timeout + if ( _tcpSocket.error() == QAbstractSocket::UnknownSocketError ) + { + _coreObject->Log.logEvent("warning", QStringLiteral("timeout while attempting to sync with %1 (2)").arg(_clientLabel)); + } + + // other errors, like establishing connection/refused + if ( _tcpSocket.error() != QAbstractSocket::UnknownSocketError ) + { + _coreObject->Log.logEvent("warning", QStringLiteral("error while syncing with %1: %2").arg(_clientLabel, _tcpSocket.errorString())); + } + + // stop timer and close socket + _timeOut.stop(); + _coreObject->Log.logEvent("tcp", QStringLiteral("%1 scheduled for deletion").arg(_clientLabel) ); + _tcpSocket.disconnectFromHost(); + this->deleteLater(); +} diff --git a/src/TcpTasks/SyncClient/onsyncread.cpp b/src/TcpTasks/SyncClient/onsyncread.cpp new file mode 100644 index 0000000..c0f0b73 --- /dev/null +++ b/src/TcpTasks/SyncClient/onsyncread.cpp @@ -0,0 +1,111 @@ +#include "syncclient.h" + +void SyncClient::onSyncRead() +{ + // reset timeout after receiving (any) data + _timeOut.start(); + + // read from tcp connection and append to buffer + QByteArray receiveBuffer = _tcpSocket.readAll(); + _rxBuffer.append( receiveBuffer ); + + // prevent large heaps of text -- log only relevant message, not masterserver data + if ( receiveBuffer.length() > 30 ) + { + // log size of data + _coreObject->Log.logEvent("tcp", QStringLiteral("%1 sent %2 characters") + .arg(_clientLabel, QString::number(receiveBuffer.length()) ) ); + } + else + { + // log message + _coreObject->Log.logEvent("tcp", QStringLiteral("%1 sent '%2'") + .arg(_clientLabel, receiveBuffer.data()) ); + } + + // remote masterserver opens with secure challenge + if ( _rxBuffer.contains("secure") ) + { + // parse to hash + QMultiHash<QString, QString> receiveData = parseGameSpy0Buffer(_rxBuffer.toLatin1()); + + // generate response + QStringList response = replyQuery(receiveData); + + // return response + _tcpSocket.write(response.join("").toLatin1()); + + // sync request + QString request = QStringLiteral("\\sync\\%1\\msid\\%2") + .arg(_coreObject->Settings.SyncerSettings.syncGames, + _coreObject->masterserverIdentity); + _tcpSocket.write(request.toLatin1()); + + // all relevant information received. clear buffer for next interaction + _rxBuffer = ""; + return; + } + + if ( _rxBuffer.contains("final") ) + { + // parse to hash: receivedData format is {gamename} => {string of addresses} + QMultiHash<QString, QString> receiveData = parseGameSpy0Buffer(_rxBuffer.toLatin1()); + receiveData.remove("final"); // prevent "final" from registering as gamename + + // count number of addresses for logging + int totalServerCount = 0; + + // use transaction for SQLite + QSqlDatabase::database().transaction(); + + // parse to list of list of <ServerInfo> + QHashIterator<QString,QString> receiveDataIterator(receiveData); + while ( receiveDataIterator.hasNext() ) + { + // {gamename} => {string of addresses} + receiveDataIterator.next(); + QString gamename = receiveDataIterator.key(); + QString addressBufferList = receiveDataIterator.value(); + + // split address list in single addresses + QStringListIterator listIterator( addressBufferList.split(" ", QString::SkipEmptyParts) ); + while ( listIterator.hasNext() ) + { + // address (ip:port) + QString addr = listIterator.next(); + + // older Qt5 masterservers sync in ::ffff:127.0.0.1 format, trim the '::ffff:' + addr.remove("::ffff:"); + + // (address cont.) + QStringList address = addr.split(':'); + unsigned short remotePort = address.takeLast().toUShort(); + QString remoteAddr = address.join(":"); // IPv4 has only 1 element, IPv6 has 4 that need joining with ':' + + // valid address? + if ( ! QHostAddress(remoteAddr).isNull() and remotePort > 0 ) + { + // if it does not exist in the db, insert + if ( ! updateSyncedServer(remoteAddr, remotePort) ) + { + // add + insertServer(remoteAddr, remotePort, gamename, false); + } + } + totalServerCount++; + } // has next address + } // has next game + + // commit SQLite + QSqlDatabase::database().commit(); + + // report in log + _coreObject->Log.logEvent("sync", QStringLiteral("received %1 servers in %2 games from %3") + .arg( QString::number(totalServerCount), + QString::number(receiveData.count()), + _clientLabel) + ); + } // if final + + // else keep appending data until \\final\\ is received +} diff --git a/src/TcpTasks/SyncClient/syncclient.cpp b/src/TcpTasks/SyncClient/syncclient.cpp new file mode 100644 index 0000000..a5a1531 --- /dev/null +++ b/src/TcpTasks/SyncClient/syncclient.cpp @@ -0,0 +1,27 @@ +#include "syncclient.h" + +SyncClient::SyncClient(const QSharedPointer<CoreObject> &coreObject, + const QString &remoteHost, + const unsigned short int &remotePort) +{ + // create local access + this->_coreObject = coreObject; + + // connect events and functions + connect(&_tcpSocket, &QTcpSocket::connected, this, &SyncClient::onSyncConnect); + connect(&_tcpSocket, &QTcpSocket::readyRead, this, &SyncClient::onSyncRead); + connect(&_tcpSocket, &QTcpSocket::disconnected, this, &SyncClient::onSyncDisconnect); + connect(&_timeOut, &QTimer::timeout, this, &SyncClient::onSyncTimeOut); + + // convenience label to found + _clientLabel = QStringLiteral("%1:%2") + .arg(remoteHost, QString::number(remotePort)); + + // set timeout + _timeOut.setInterval( _timeOutTime_ms ); + _timeOut.start(); // connection timout time === read timeout time + + // connect to remote masterserver + _tcpSocket.connectToHost(remoteHost, remotePort); + _coreObject->Log.logEvent("tcp", QStringLiteral("connecting to %1").arg(_clientLabel)); +} diff --git a/src/TcpTasks/SyncClient/syncclient.h b/src/TcpTasks/SyncClient/syncclient.h new file mode 100644 index 0000000..8cf253f --- /dev/null +++ b/src/TcpTasks/SyncClient/syncclient.h @@ -0,0 +1,50 @@ +#ifndef SYNCCLIENT_H +#define SYNCCLIENT_H + +#include <QTimer> +#include <QTcpSocket> +#include <QHostAddress> + +#include "Core/CoreObject/coreobject.h" +#include "Database/Common/commonactions.h" +#include "Protocols/GameSpy0/gamespy0.h" +#include "Protocols/GameSpy0/securevalidate.h" + +class SyncClient: public QObject +{ + Q_OBJECT +public: + SyncClient(const QSharedPointer<CoreObject> &coreObject, + const QString &remoteHost, + const unsigned short int &remotePort); + +private: + QSharedPointer<CoreObject> _coreObject; + const int _timeOutTime_ms = 7500; + + // tcp client handles + QTcpSocket _tcpSocket; + QTimer _timeOut; + QString _rxBuffer = ""; + QString _clientLabel; + + // helpers + int _queryId = 0; + + // functions + QStringList replyQuery(const QMultiHash<QString, QString> &query); + +private: // update sync time in database + bool updateSyncedServer(const QString &serverAddress, + const unsigned short &serverPort); + +private slots: + void onSyncConnect(); + void onSyncRead(); + void onSyncDisconnect(); + void onSyncTimeOut(); + +signals: +}; + +#endif // SYNCCLIENT_H diff --git a/src/TcpTasks/SyncClient/syncreplyquery.cpp b/src/TcpTasks/SyncClient/syncreplyquery.cpp new file mode 100644 index 0000000..d36637f --- /dev/null +++ b/src/TcpTasks/SyncClient/syncreplyquery.cpp @@ -0,0 +1,49 @@ +#include "syncclient.h" + +QStringList SyncClient::replyQuery(const QMultiHash<QString, QString> &query) +{ + // initialise output + QStringList queryResponse; + + // gamespy uses incrementing query ids in the messages + _queryId = ( (_queryId > 99) ? 1 : _queryId + 1 ); + int querySubId = 1; + + // secure response + if ( query.contains("secure") and _coreObject->SupportedGames.contains(TYPE_GAMENAME)) + { + // sanity checks + QByteArray secure = query.value("secure", "").toLatin1(); + QByteArray cipher = _coreObject->SupportedGames.value(TYPE_GAMENAME).cipher.toLatin1(); + int enctype = query.value("enctype", "0").toInt(); + QString validate = returnValidate(cipher, secure, enctype); + + queryResponse.append( + QStringLiteral("\\validate\\%1\\queryid\\%2.%3") + .arg(validate, QString::number(_queryId), QString::number(querySubId++)) + ); + } + + // basic + if ( query.contains("basic") ) + { + queryResponse.append( + QStringLiteral("\\gamename\\%1" + "\\gamever\\%2" + "\\location\\0" + "\\queryid\\%3.%4") + .arg(TYPE_GAMENAME, + SHORT_VER, + QString::number(_queryId), + QString::number(querySubId++) + ) + ); + } + + + + // end query with final + queryResponse.append("\\final\\"); + + return queryResponse; +} diff --git a/src/TcpTasks/SyncClient/updatesyncedserver.cpp b/src/TcpTasks/SyncClient/updatesyncedserver.cpp new file mode 100644 index 0000000..354c6a5 --- /dev/null +++ b/src/TcpTasks/SyncClient/updatesyncedserver.cpp @@ -0,0 +1,27 @@ +#include "syncclient.h" + +bool SyncClient::updateSyncedServer(const QString &serverAddress, + const unsigned short &serverPort) +{ + // update existing entry, but do not insert. + QSqlQuery q; + QString updateString; + + // update with available values + updateString = "UPDATE serverlist SET " + "dt_sync = :timestamp " + "WHERE ip = :ip " + "AND queryport = :queryport"; + + // bind values and execute + q.prepare(updateString); + q.bindValue(":ip", serverAddress); + q.bindValue(":queryport", serverPort); + q.bindValue(":timestamp", QDateTime::currentSecsSinceEpoch() ); + + if ( ! q.exec() ) + return reportQuery(q); + + // was a row updated? + return (q.numRowsAffected() > 0); +} diff --git a/src/TcpTasks/Updater/onsynctickeraction.cpp b/src/TcpTasks/Updater/onsynctickeraction.cpp new file mode 100644 index 0000000..53214ce --- /dev/null +++ b/src/TcpTasks/Updater/onsynctickeraction.cpp @@ -0,0 +1,20 @@ +#include "syncupdater.h" + +void SyncUpdater::onSyncTickerAction() +{ + // exists? + if ( _syncIndex < _coreObject->Settings.SyncerSettings.syncServers.length() ) + { + // retrieve sync settings from config + SyncServer syncServ = _coreObject->Settings.SyncerSettings.syncServers.at(_syncIndex); + _syncIndex++; + + // execute sync with remote masterserver + new SyncClient(_coreObject, syncServ.remoteAddress, syncServ.listenPort); + } + else + { + // last entry. turn off timer + _syncTicker.stop(); + } +} diff --git a/src/TcpTasks/Updater/scheduleupdater.cpp b/src/TcpTasks/Updater/scheduleupdater.cpp new file mode 100644 index 0000000..7a96d6e --- /dev/null +++ b/src/TcpTasks/Updater/scheduleupdater.cpp @@ -0,0 +1,29 @@ +#include "syncupdater.h" + +bool SyncUpdater::scheduleUpdater() +{ + // schedule sync X seconds apart (prevent network spikes) + connect(&_syncTicker, &QTimer::timeout, this, &SyncUpdater::onSyncTickerAction); + _syncTicker.setInterval( _graceTime_ms ); + + // set update timer + connect(&_updaterTimer, &QTimer::timeout, [this] + { + // reset and start ticker + _syncIndex = 0; + _syncTicker.start(); + }); + + _updaterTimer.setInterval( _coreObject->Settings.SyncerSettings.syncInterval_s * 1000); + _updaterTimer.start(); + + // complete startup + _coreObject->Log.logEvent("info", QStringLiteral("sync with other masterservers every %1 seconds") + .arg(_coreObject->Settings.SyncerSettings.syncInterval_s)); + + // run immediately at startup + _syncIndex = 0; + _syncTicker.start(); + + return true; +} diff --git a/src/TcpTasks/Updater/syncupdater.cpp b/src/TcpTasks/Updater/syncupdater.cpp new file mode 100644 index 0000000..da52f12 --- /dev/null +++ b/src/TcpTasks/Updater/syncupdater.cpp @@ -0,0 +1,7 @@ +#include "syncupdater.h" + +SyncUpdater::SyncUpdater(const QSharedPointer<CoreObject> &coreObject) +{ + // create local access + this->_coreObject = coreObject; +} diff --git a/src/TcpTasks/Updater/syncupdater.h b/src/TcpTasks/Updater/syncupdater.h new file mode 100644 index 0000000..9bd2299 --- /dev/null +++ b/src/TcpTasks/Updater/syncupdater.h @@ -0,0 +1,33 @@ +#ifndef SYNCUPDATER_H +#define SYNCUPDATER_H + +#include <QTimer> +#include <QHostInfo> + +#include "Core/CoreObject/coreobject.h" +#include "Database/Common/commonactions.h" +#include "TcpTasks/SyncClient/syncclient.h" + +class SyncUpdater: public QObject +{ + Q_OBJECT +public: + SyncUpdater(const QSharedPointer<CoreObject> &coreObject); + bool scheduleUpdater(); + +private: + QSharedPointer<CoreObject> _coreObject; + const int _graceTime_ms = 5000; + + // update/ticker timer + QTimer _updaterTimer; + QTimer _syncTicker; + + // index + int _syncIndex = 0; + +private slots: + void onSyncTickerAction(); +}; + +#endif // SYNCUPDATER_H |
