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