/*
* connectwindow.cpp
*
* Created on: 28.01.2013
* Author: sr
*/
#include "connectwindow.h"
#include "../../shared/settings.h"
#include "../../shared/network.h"
#include "../../shared/util.h"
#include "../net/serverconnection.h"
#include <QNetworkInterface>
#define UDPBUFSIZ 9000
#define SALT_LEN 18
ConnectWindow::ConnectWindow(QWidget *parent) :
QDialog(parent), _connected(false), _timerDiscover(0), _timerHide(0), _connection(NULL), _state(Idle),
_hashErrorCount(0), _hashSslErrorCount(0), _certErrorCount(0), _ipErrorCount(0), _discoveryInterval(800)
{
setupUi(this);
//
connect(cmdOK, SIGNAL(clicked()), this, SLOT(onOkClick()));
connect(cmdCancel, SIGNAL(clicked()), this, SLOT(onCancelClick()));
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.");
}
connect(&_discoverySocket, SIGNAL(readyRead()), this, SLOT(onUdpReadyRead()));
this->setState(Idle);
}
ConnectWindow::~ConnectWindow()
{
}
void ConnectWindow::setConnected(const bool connected)
{
_connected = connected;
this->updateState();
if (_state == Scanning)
{
killTimer(_timerDiscover);
_discoveryInterval = 1000;
_timerDiscover = startTimer(_discoveryInterval);
}
}
void ConnectWindow::setState(const ConnectionState state)
{
if (_state != state)
{
_state = state;
this->updateState();
}
}
void ConnectWindow::updateState()
{
txtName->setEnabled(_state == Idle && !_connected);
if (_connected)
{
cmdOK->setEnabled(true);
cmdOK->setText(tr("&Disconnect"));
lblStatus->setText(tr("Connected."));
txtName->setEnabled(false);
return;
}
if (_state != Idle)
cmdOK->setText(tr("&Stop"));
else
cmdOK->setText(tr("&Connect"));
switch (_state)
{
case Idle:
lblStatus->setText(tr("Ready to connect; please enter session name."));
break;
case Scanning:
lblStatus->setText(tr("Scanning for session %1.").arg(txtName->text()));
_timerDiscover = startTimer(_discoveryInterval);
break;
case Connecting:
lblStatus->setText(tr("Found session, connecting..."));
break;
case AwaitingChallenge:
lblStatus->setText(tr("Waiting for server challenge..."));
break;
case AwaitingChallengeResponse:
lblStatus->setText(tr("Replied to challenge, sent own..."));
break;
case LoggingIn:
lblStatus->setText(tr("Logging in..."));
break;
case Connected:
lblStatus->setText(tr("Connection established!"));
break;
case InvalidIpList:
case InvalidHash:
case InvalidCert:
case InvalidSslHash:
lblError->setText(tr("Invalid hash: %1; invalid cert: %2; invalid iplist: %3; invalid sslhash: %4")
.arg(_hashErrorCount).arg(_certErrorCount).arg(_ipErrorCount).arg(_hashSslErrorCount));
break;
}
}
/**
* Overrides
*/
void ConnectWindow::timerEvent(QTimerEvent* event)
{
if (event->timerId() == _timerDiscover)
{
killTimer(_timerDiscover);
if (_connected || _state != Scanning) // Not scanning, bail out
return;
if (_discoveryInterval < 30000)
_discoveryInterval += 100;
_timerDiscover = startTimer(_discoveryInterval);
// Don't send packet if we're trying to connect
if (_connection != NULL)
return;
// Send discovery
_packet.reset();
QByteArray iplist(Network::interfaceAddressesToString().toUtf8());
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");
// End send discovery
}
else if(event->timerId() == _timerHide)
{
killTimer(_timerHide);
_timerHide = 0;
this->hide();
}
else
// Unknown/Old timer id, kill it
killTimer(event->timerId());
}
void ConnectWindow::closeEvent(QCloseEvent *e)
{
e->ignore();
this->hide();
}
/**
* Slots
*/
void ConnectWindow::onOkClick()
{
if (_timerHide)
killTimer(_timerHide);
_timerHide = 0;
if (_timerDiscover)
killTimer(_timerDiscover);
if (_connected || _state != Idle)
{
// Stop or disconnect
_timerDiscover = 0;
emit disconnect();
this->setState(Idle);
}
else
{
// Connect (scan for session)
_discoveryInterval = 800;
_nameBytes = txtName->text().toUtf8();
_timerDiscover = startTimer(_discoveryInterval);
_hashErrorCount = _hashSslErrorCount = _certErrorCount = _ipErrorCount = 0;
this->setState(Scanning);
}
}
void ConnectWindow::onCancelClick()
{
this->hide();
}
void ConnectWindow::onUdpReadyRead()
{
char data[UDPBUFSIZ];
QHostAddress addr;
quint16 port;
while (_discoverySocket.hasPendingDatagrams())
{
const qint64 size = _discoverySocket.readDatagram(data, UDPBUFSIZ, &addr, &port);
if (size <= 0 || _connection != NULL)
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;
this->setState(InvalidIpList);
this->setState(Scanning);
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;
this->setState(InvalidHash);
this->setState(Scanning);
continue;
}
// Otherwise it's a valid reply, try to connect
_connection = new ServerConnection(addr.toString(), (quint16)QString::fromUtf8(port).toInt(), _nameBytes, cert);
connect(_connection, SIGNAL(stateChange(ConnectWindow::ConnectionState)), this, SLOT(onConnectionStateChange(ConnectWindow::ConnectionState)));
connect(_connection, SIGNAL(destroyed(QObject*)), this, SLOT(onConnectionClosed(QObject*)));
}
}
void ConnectWindow::onConnectionStateChange(ConnectWindow::ConnectionState state)
{
bool reset = (_state == Scanning);
if (state == InvalidSslHash)
++_hashSslErrorCount;
this->setState(state);
if (reset)
_state = Scanning;
if (state == Connected)
{
QObject::disconnect(_connection, SIGNAL(stateChange(ConnectWindow::ConnectionState)), this, SLOT(onConnectionStateChange(ConnectWindow::ConnectionState)));
QObject::disconnect(_connection, SIGNAL(destroyed(QObject*)), this, SLOT(onConnectionClosed(QObject*)));
emit connected(_connection);
_connection = NULL;
_timerHide = startTimer(2000);
}
}
void ConnectWindow::onConnectionClosed(QObject* connection)
{
_connection = NULL;
}