summaryrefslogblamecommitdiffstats
path: root/src/client/connectwindow/connectwindow.cpp
blob: abc33e8ca8f42e58025920d2e6e1b86ff8461533 (plain) (tree)
























































































































































































                                                                                                                                                      




                                                


































































































                                                                                                                                                                           
/*
 * 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();
}

void ConnectWindow::showEvent(QShowEvent* event)
{
	txtName->setFocus();
}

/**
 * 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;
}