aboutsummaryrefslogtreecommitdiff
path: root/src/TcpTasks
diff options
context:
space:
mode:
Diffstat (limited to 'src/TcpTasks')
-rw-r--r--src/TcpTasks/ListenClientHandler/compileserverlist.cpp39
-rw-r--r--src/TcpTasks/ListenClientHandler/compilesynclist.cpp35
-rw-r--r--src/TcpTasks/ListenClientHandler/listenclientdisconnect.cpp21
-rw-r--r--src/TcpTasks/ListenClientHandler/listenclienthandler.cpp28
-rw-r--r--src/TcpTasks/ListenClientHandler/listenclienthandler.h49
-rw-r--r--src/TcpTasks/ListenClientHandler/onlistenclientread.cpp185
-rw-r--r--src/TcpTasks/ListenServer/listenserver.cpp7
-rw-r--r--src/TcpTasks/ListenServer/listenserver.h31
-rw-r--r--src/TcpTasks/ListenServer/onlistenconnection.cpp7
-rw-r--r--src/TcpTasks/ListenServer/tcplisten.cpp25
-rw-r--r--src/TcpTasks/SyncClient/onsyncconnect.cpp10
-rw-r--r--src/TcpTasks/SyncClient/onsyncdisconnect.cpp49
-rw-r--r--src/TcpTasks/SyncClient/onsyncread.cpp111
-rw-r--r--src/TcpTasks/SyncClient/syncclient.cpp27
-rw-r--r--src/TcpTasks/SyncClient/syncclient.h50
-rw-r--r--src/TcpTasks/SyncClient/syncreplyquery.cpp49
-rw-r--r--src/TcpTasks/SyncClient/updatesyncedserver.cpp27
-rw-r--r--src/TcpTasks/Updater/onsynctickeraction.cpp20
-rw-r--r--src/TcpTasks/Updater/scheduleupdater.cpp29
-rw-r--r--src/TcpTasks/Updater/syncupdater.cpp7
-rw-r--r--src/TcpTasks/Updater/syncupdater.h33
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