#include <QNetworkInterface>
#include "../../shared/settings.h"
#include "../../shared/network.h"
#include "../../shared/util.h"
#include "serverdiscovery.h"
#include <assert.h>
/***************************************************************************//**
* Ctor
*/
ServerDiscovery::ServerDiscovery(QObject *parent) : QObject(parent)
{
_hashErrorCount = 0;
_ipErrorCount = 0;
/* Try to get a UDP port for server discovery */
int tries = 10;
while (tries-- != 0)
{
const quint16 port = (quint16)(qrand() % 10000) + 10000;
if (_discoverySocket.bind(QHostAddress::Any, port))
break;
if (tries == 0)
qFatal("Could not bind to any UDP port for server discovery.");
}
// Handle incoming messages
connect(&_discoverySocket, SIGNAL(readyRead()), this, SLOT(onUdpReadyRead()));
/* Setup the discovery timer */
_discoveryTimer.setInterval(500);
_discoveryTimer.setSingleShot(true);
//
connect(&_discoveryTimer, SIGNAL(timeout()), this, SLOT(doDiscovery()));
}
/***************************************************************************//**
* Dtor
*/
ServerDiscovery::~ServerDiscovery()
{
}
/***************************************************************************//**
* @brief start
*/
void ServerDiscovery::start(const QByteArray& sessionName)
{
//assert(!this->isActive());
// Set the session which is searched
_nameBytes = sessionName;
// Enable signal emittance
this->blockSignals(false);
// Reset the error counters
_hashErrorCount = _ipErrorCount = 0;
// reset anbd start the discovery timer
_discoveryTimer.setInterval(500);
_discoveryTimer.start();
}
/***************************************************************************//**
* @brief stop
*/
void ServerDiscovery::stop()
{
assert(this->isActive());
//Bock further signal emittance
this->blockSignals(true);
_discoveryTimer.stop();
}
/*******************************************************************************
* SLOTS
***************************************************************************//**
* @brief ConnectWindow::doDiscovery
*/
void ServerDiscovery::doDiscovery()
{
// Send discovery
_packet.reset();
QByteArray iplist(Network::interfaceAddressesToString().toUtf8());
// qDebug
QByteArray salt1(SALT_LEN, 0);
if (_salt2.size() < SALT_LEN)
_salt2.resize(SALT_LEN);
for (int i = 0; i < SALT_LEN; ++i) {
salt1[i] = qrand() & 0xff;
_salt2[i] = qrand() & 0xff;
}
_packet.reset();
_packet.setField(_HASH, genSha1(&_nameBytes, &salt1, &iplist));
_packet.setField(_SALT1, salt1);
_packet.setField(_SALT2, _salt2);
_packet.setField(_IPLIST, iplist);
foreach (QNetworkInterface interface, QNetworkInterface::allInterfaces())
{
foreach (QNetworkAddressEntry entry, interface.addressEntries())
{
if (!entry.broadcast().isNull() && entry.ip() != QHostAddress::LocalHost && entry.ip() != QHostAddress::LocalHostIPv6)
{
qDebug() << "Broadcasting to " << entry.broadcast().toString();
if (!_packet.writeMessage(&_discoverySocket, entry.broadcast(), SERVICE_DISCOVERY_PORT))
qDebug("FAILED");
}
}
}
qDebug("Broadcasting to 255.255.255.255");
if (!_packet.writeMessage(&_discoverySocket, QHostAddress::Broadcast, SERVICE_DISCOVERY_PORT))
qDebug("FAILED");
// Start the timer again with a larger interval
if (_discoveryTimer.interval() < 5000)
_discoveryTimer.setInterval(_discoveryTimer.interval() * 2);
_discoveryTimer.start();
}
/***************************************************************************//**
* Handle incoming service discovery packets.
*/
void ServerDiscovery::onUdpReadyRead()
{
char data[UDPBUFSIZ];
QHostAddress addr;
quint16 port;
while (_discoverySocket.hasPendingDatagrams())
{
// Discard any packets if discovery is stopped
if (!this->isActive()){
_discoverySocket.readDatagram(NULL, 0);
continue;
}
const qint64 size = _discoverySocket.readDatagram(data, UDPBUFSIZ, &addr, &port);
if (size <= 0) //|| _connection != NULL) // TODO CHECK
continue;
_packet.reset();
if (!_packet.readMessage(data, (quint32)size))
continue;
// Valid packet, process it:
const QByteArray hash(_packet.getFieldBytes(_HASH));
const QByteArray iplist(_packet.getFieldBytes(_IPLIST));
const QByteArray port(_packet.getFieldBytes(_PORT));
const QByteArray cert(_packet.getFieldBytes(_CERT));
// Check if the source IP of the packet matches any of the addresses given in the IP list
if (!Network::isAddressInList(QString::fromUtf8(iplist), addr.toString()))
{
++_ipErrorCount;
emit error(ErrorType::InvalidIpList, _hashErrorCount);
continue;
}
// If so, check if the submitted hash seems valid
if (genSha1(&_nameBytes, &_salt2, &iplist, &port, &cert) != hash)
{
// did not match local session name, or other data was spoofed
++_hashErrorCount;
emit error(ErrorType::InvalidHash, _ipErrorCount);
continue;
}
/* Otherwise it's a valid reply */
qDebug() << "Server detected:"
<< addr.toString() + ":" + QString::fromUtf8(port) + "/" + _nameBytes;
// Tell that a server hs been found
emit serverDetected(addr.toString(), (quint16)QString::fromUtf8(port).toInt(), _nameBytes, cert);
// Stop the discovery
this->stop();
}
}