/* * connectwindow.cpp * * Created on: 28.01.2013 * Author: sr */ #include #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); connect(_ui->cmdOK, SIGNAL(clicked()), this, SLOT(onOkClick())); connect(_ui->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); lblCheckmark->hide(); } 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. */ void ConnectWindow::updateState() { _ui->txtName->setEnabled(_state == Idle && !_connected); if (_connected) { _ui->lblCheckmark->setVisible(true); _ui->cmdOK->setEnabled(true); _ui->cmdOK->setText(tr("&Disconnect")); _ui->lblStatus->setText(tr("Connected.")); _ui->txtName->setEnabled(false); return; } if (_state != Idle) _ui->cmdOK->setText(tr("&Stop")); else _ui->cmdOK->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->txtName->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->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 */ /** * 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(); lblCheckmark->hide(); } else // Unknown/Old timer id, kill it killTimer(event->timerId()); } /** * Close Event e 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->txtName->setFocus(); } /* * Slots */ /** * Handle click on Connect/Disconnect button. * If already connected --> Stop/disconnect. * Else scanning for given sessionId. */ 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 = _ui->txtName->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::onCancelClick() { 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); } } /** * Set _connection = NULL. * @param connection */ void ConnectWindow::onConnectionClosed(QObject* connection) { _connection = NULL; }