summaryrefslogblamecommitdiffstats
path: root/src/client/connectwindow/connectwindow.cpp
blob: 0a09b0e67abf5907e5e17e80584ad718f9ffb677 (plain) (tree)
1
2
3
4
5
6
7
8






                          
                            


                                  
                                    

                          



                      



                                
                                               



                                                                          
 

                             

                                               
 




                                                                                       







                                                                                       
                                                                                      


                             
                                 
 




                                                          











                                                                




                                               








                                                         

                                             
                                                                          
   

                                 
                                                                     


                       

                                                                
                                                          

                                                       



                           
                                                          
            
                                                             



                       
                                                                                            

                      
                                                                                                       


                                                                
                                                                            

                               
                                                                               

                                       
                                                                                 

                       
                                                             

                       
                                                                       




                            
                                                                                                                         




                                                                                                               
  


            



                                                              


















































                                                                                                                                                      
                                                       





                                                
   
                                              

           





                                              



                                                    

                                                
                                      

 
  


        




                                             
                                     
 
                        
                                      

                                                       













                                              
                                                                





                                                                                           



                                      
                               



                     


                                             










































                                                                                                                                                               



                                                                                

















                                                                                                                                                                           
   
                                                  

                    



                                                           
/*
 * connectwindow.cpp
 *
 *  Created on: 28.01.2013
 *      Author: sr
 */

#include <QNetworkInterface>
#include "../../shared/settings.h"
#include "../../shared/network.h"
#include "../../shared/util.h"
#include "../net/serverconnection.h"
#include "connectwindow.h"
#include "ui_connect.h"

#define UDPBUFSIZ 9000
#define SALT_LEN 18

/**
 * Initialize Connection Window.
 * @param parent
 */
ConnectWindow::ConnectWindow(QWidget *parent) :
	QWidget(parent), _ui(new Ui::ConnectWindow), _connected(false),
	_timerDiscover(0), _timerHide(0), _connection(NULL), _state(Idle),
	_hashErrorCount(0), _hashSslErrorCount(0), _certErrorCount(0),
	_ipErrorCount(0), _discoveryInterval(800)
{
	// Initialize the GUI
	_ui->setupUi(this);
	// Set page 0 as default
	_ui->stackedWidget->setCurrentIndex(0);

	// Set actions of buttons
	connect(_ui->btn_connection, SIGNAL(clicked()), this, SLOT(onBtnConnection()));
	connect(_ui->btn_hide, SIGNAL(clicked()), this, SLOT(onBtnHide()));

	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(){}

/**
 * Set Client as Connected (true) or Disconnected (false).
 * After settings updateState() is called.
 * @param connected
 */
void ConnectWindow::setConnected(const bool connected)
{
	_connected = connected;
	this->updateState();
	if (_state == Scanning)
	{
		killTimer(_timerDiscover);
		_discoveryInterval = 1000;
		_timerDiscover = startTimer(_discoveryInterval);
	}
}

/**
 * Set current state of Client.
 * After setting state updateState() is called.
 * @param state
 */
void ConnectWindow::setState(const ConnectionState state)
{
	if (_state != state)
	{
		_state = state;
		this->updateState();
	}
}

/**
 * Handle changes in state and update window.
 * Also changing TextLabel which shows the user what the program is doing.
 */
void ConnectWindow::updateState()
{
	_ui->lineEditName->setEnabled(_state == Idle && !_connected);

	if (_connected)
	{
		_ui->btn_connection->setEnabled(true);
		_ui->btn_connection->setText(tr("&Disconnect"));
		_ui->lblStatus->setText(tr("Connected."));
		_ui->lineEditName->setEnabled(false);
		_ui->stackedWidget->setCurrentIndex(1);
		return;
	}

	if (_state != Idle)
		_ui->btn_connection->setText(tr("&Stop"));
	else
		_ui->btn_connection->setText(tr("&Connect"));

	switch (_state)
	{
	case Idle:
		_ui->lblStatus->setText(tr("Ready to connect; please enter session name."));
		break;
	case Scanning:
		_ui->lblStatus->setText(tr("Scanning for session %1.").arg(_ui->lineEditName->text()));
		_timerDiscover = startTimer(_discoveryInterval);
		break;
	case Connecting:
		_ui->lblStatus->setText(tr("Found session, connecting..."));
		break;
	case AwaitingChallenge:
		_ui->lblStatus->setText(tr("Waiting for server challenge..."));
		break;
	case AwaitingChallengeResponse:
		_ui->lblStatus->setText(tr("Replied to challenge, sent own..."));
		break;
	case LoggingIn:
		_ui->lblStatus->setText(tr("Logging in..."));
		break;
	case Connected:
		_ui->lblStatus->setText(tr("Connection established!"));
		break;
	case InvalidIpList:
	case InvalidHash:
	case InvalidCert:
	case InvalidSslHash:
		_ui->lblStatus->setText(tr("Invalid hash: %1; invalid cert: %2; invalid iplist: %3; invalid sslhash: %4")
			.arg(_hashErrorCount).arg(_certErrorCount).arg(_ipErrorCount).arg(_hashSslErrorCount));
		break;
	}
}

/*
 * Overrides
 */

/**
 * Called when a Qt timer fires; used for server discovery and
 * auto-hiding the connect dialog.
 */
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();
		_ui->stackedWidget->setCurrentIndex(0);
	}
	else
		// Unknown/Old timer id, kill it
		killTimer(event->timerId());
}

/**
 * Handle incoming closeEvent and hide window.
 * @param e
 */
void ConnectWindow::closeEvent(QCloseEvent *e)
{
	e->ignore();
	this->hide();
}

/**
 * Gives the keyboard input focus to the input line.
 * @param event
 */
void ConnectWindow::showEvent(QShowEvent* event)
{
	_ui->lineEditName->setFocus();
}

/*
 * Slots
 */

/**
 * Handle click on Connect/Disconnect button.
 * If already connected --> Stop/disconnect.
 * Else scanning for given sessionId.
 */
void ConnectWindow::onBtnConnection()
{
	if (_timerHide){
		killTimer(_timerHide);
		_ui->stackedWidget->setCurrentIndex(0);
	}
	_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 = _ui->lineEditName->text().toUtf8();
		_timerDiscover = startTimer(_discoveryInterval);
		_hashErrorCount = _hashSslErrorCount = _certErrorCount = _ipErrorCount = 0;
		this->setState(Scanning);
	}
}

/**
 * Handle click on Cancel/Hide Button.
 * Just hide the window.
 */
void ConnectWindow::onBtnHide()
{
	this->hide();
}

/**
 * Handle incoming service discovery packets.
 */
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*)));
	}
}

/**
 * Handle connection state changes and update member variables describing state.
 * @param state
 */
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);
	}
}

/**
 * If connection is closed set _connection = NULL.
 * @param connection
 */
void ConnectWindow::onConnectionClosed(QObject* connection)
{
	_connection = NULL;
}