/*
* client.cpp
*
* Created on: 18.01.2013
* Author: sr
*/
#include "client.h"
#include "../util/global.h"
#include "../../shared/settings.h"
#include "../../shared/util.h"
#include <QSslSocket>
#include <QHostAddress>
#include <QPixmap>
#include <cassert>
#define CHALLENGE_LEN 20
int Client::_clientIdCounter = 0;
Client::Client(QSslSocket* socket) :
_socket(socket), _authed(0), _timerDelete(0), _desiredProjectionSource(0), _isProjectionSource(false),
_currentProjectionSource(0), _vncPort(0), _activeVncClient(false), _isTutor(false)
{
assert(socket != NULL);
_id = ++_clientIdCounter;
_ip = _socket->peerAddress().toString();
qDebug("*** Client %s created.", qPrintable(_ip));
// Connect important signals
connect(_socket, SIGNAL(disconnected()), this, SLOT(onClosed()));
connect(_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)));
connect(_socket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(onSslErrors(const QList<QSslError> &)));
connect(_socket, SIGNAL(readyRead()), this, SLOT(onDataArrival()));
// Send challenge
_challenge.resize(CHALLENGE_LEN);
for (int i = 0; i < CHALLENGE_LEN; ++i)
_challenge[i] = qrand() & 0xff;
_toClient.reset();
_toClient.setField(_ID, _CHALLENGE);
_toClient.setField(_CHALLENGE, _challenge);
_toClient.writeMessage(_socket);
// give client 3 seconds to complete handshake
_timerIdAuthTimeout = startTimer(3000);
_timerPingTimeout = startTimer(5000);
_pingTimeout = QDateTime::currentMSecsSinceEpoch() + PING_TIMEOUT_MS;
}
Client::~Client()
{
if (_socket != NULL)
{
qCritical("**** SOCKET DELETE IN DESTRUCTOR");
_socket->deleteLater();
}
qDebug("*** Client %s destroyed.", qPrintable(_ip));
}
void Client::timerEvent(QTimerEvent* event)
{
if (event->timerId() == _timerPingTimeout)
{
if (_pingTimeout < QDateTime::currentMSecsSinceEpoch())
{
qDebug() << "Client" << _ip << "has a ping timeout.";
killTimer(_timerPingTimeout);
this->disconnect();
}
}
else if (event->timerId() == _timerIdAuthTimeout)
{
// Client did not send login request within 3 seconds
killTimer(_timerIdAuthTimeout);
_timerIdAuthTimeout = 0;
this->disconnect();
}
else if (event->timerId() == _timerDelete)
{
if (_socket == NULL || _socket->state() == QAbstractSocket::UnconnectedState)
{
if (_socket != NULL)
_socket->deleteLater();
_socket = NULL;
killTimer(_timerDelete);
this->deleteLater();
return;
}
_socket->abort();
qDebug("A socket is still pending...");
}
else
killTimer(event->timerId());
}
void Client::sendMessage(NetworkMessage& message)
{
if (_socket == NULL || _socket->state() != QAbstractSocket::ConnectedState)
return;
message.writeMessage(_socket);
if (!message.writeComplete())
{
qCritical() << "SendMessage to client " << _name << "@" << _ip << " failed!";
}
}
void Client::requestThumb(const int width, const int height)
{
if (_socket == NULL || _socket->state() != QAbstractSocket::ConnectedState)
{
qDebug("requestThumb called in bad state");
return;
}
_toClient.reset();
_toClient.setField(_ID, _THUMB);
_toClient.setField(_X, QString::number(width));
_toClient.setField(_Y, QString::number(height));
_toClient.writeMessage(_socket);
}
void Client::onDataArrival()
{
//
if (_socket == NULL || _socket->state() != QAbstractSocket::ConnectedState)
{
qDebug("dataArrival called in bad state");
return;
}
//qDebug() << _socket->bytesAvailable() << " bytes to read";
bool ret;
while (_socket->bytesAvailable())
{
ret = _fromClient.readMessage(_socket); // let the message read data from socket
if (!ret) // error parsing msg, disconnect client!
{
this->disconnect();
return;
}
if (_fromClient.readComplete()) // message is complete
{
this->handleMsg();
_fromClient.reset();
if (_socket == NULL)
return;
}
}
}
void Client::onSslErrors(const QList<QSslError> & errors)
{
this->disconnect();
}
void Client::onClosed()
{
this->disconnect();
}
void Client::onError(QAbstractSocket::SocketError errcode)
{
this->disconnect();
}
void Client::handleMsg()
{
_pingTimeout = QDateTime::currentMSecsSinceEpoch() + PING_TIMEOUT_MS;
const QString &id = _fromClient.getFieldString(_ID);
if (id.isEmpty())
{
qDebug("Received message with empty ID field. ignored.");
return;
}
//qDebug() << "Received message " << id;
if (_authed == 2)
{
// Following messages are only valid of the client is already authenticated
if (id == _THUMB)
{
QPixmap pixmap;
if (!pixmap.loadFromData(_fromClient.getFieldBytes("IMG")))
{
qDebug("Could not decode thumbnail image from client.");
return;
}
emit thumbUpdated(this, pixmap);
}
else if (id == _VNCSERVER)
{
// Client tells about startup of vnc server
const int port = _fromClient.getFieldString("PORT").toInt();
if (port <= 0)
{
if (_vncPort <= 0)
{
qDebug() << "Starting VNC server on client" << _name << " (" << _ip << ") failed.";
// TODO: Show message on manager
}
}
else
{
_vncRoPass = _fromClient.getFieldString("ROPASS");
_vncRwPass = _fromClient.getFieldString("RWPASS");
}
_vncPort = port;
emit vncServerStateChange(this);
}
else if (id == _VNCCLIENT)
{
// Client tells us that it started or stopped displaying a remote screen via VNC
_activeVncClient = (_fromClient.getFieldString("ENABLED").toInt() != 0);
const int other = (int)_fromClient.getFieldString("CLIENTID").toInt();
if (!_activeVncClient && other == 0)
_desiredProjectionSource = 0;
int last = _currentProjectionSource;
if (!_activeVncClient)
_currentProjectionSource = 0;
else
_currentProjectionSource = other;
emit vncClientStateChange(this, last);
}
return;
}
// Not authed yet, only care about login requests
if (_authed == 1)
{
if (id == _LOGIN)
{
killTimer(_timerIdAuthTimeout);
_timerIdAuthTimeout = 0;
ClientLogin request;
request.accept = true;
request.host = _fromClient.getFieldString("HOST");
request.name = _fromClient.getFieldString("NAME");
request.ip = _socket->peerAddress().toString();
qDebug() << "Login request by " << request.name;
// emit event, see if request is accepted
emit authenticating(this, &request);
if (!request.accept)
{
qDebug("Request denied.");
this->disconnect(); // Nope
return;
}
// Accepted
_authed = 2;
qDebug("valid, step <- 2");
_name = request.name;
_host = request.host;
_toClient.reset();
_toClient.setField(_ID, _LOGIN);
_toClient.writeMessage(_socket);
emit authenticated(this);
}
return;
}
// Did not pass challenge yet
if (_authed == 0)
{
// Waiting for challenge reply by client
if (id == _CHALLENGE)
{
QByteArray hash(_fromClient.getFieldBytes(_HASH));
QByteArray challenge(_fromClient.getFieldBytes(_CHALLENGE));
if (genSha1(&Global::sessionNameArray(), &_challenge) != hash)
{ // Challenge reply is invalid, drop client
_toClient.buildErrorMessage("Challenge reply invalid.");
_toClient.writeMessage(_socket);
this->disconnect();
return;
}
// Now answer to challenge by client
_toClient.reset();
_toClient.setField(_ID, _CHALLENGE);
_toClient.setField(_HASH, genSha1(&Global::sessionNameArray(), &challenge));
_toClient.writeMessage(_socket);
_authed = 1;
qDebug("client's challenge reply was valid, step <- 1");
}
return;
}
}
void Client::startVncServer()
{
_vncPort = 0;
_toClient.reset();
_toClient.setField(_ID, _VNCSERVER);
_toClient.setField(_ENABLE, __TRUE);
sendMessage(_toClient);
}
void Client::stopVncServer()
{
_toClient.reset();
_toClient.setField(_ID, _VNCSERVER);
_toClient.setField(_ENABLE, __FALSE);
sendMessage(_toClient);
}
void Client::stopVncClient()
{
_activeVncClient = false;
_toClient.reset();
_toClient.setField(_ID, _VNCCLIENT);
sendMessage(_toClient);
}
void Client::setTutor(bool enable)
{
_toClient.reset();
_toClient.setField(_ID, _TUTOR);
_toClient.setField(_ENABLE, _BOOL(enable));
sendMessage(_toClient);
_isTutor = enable;
}
void Client::disconnect()
{
if (_timerDelete == 0)
{
emit disconnected();
_timerDelete = startTimer(500);
qDebug("*** Client %s disconnected.", qPrintable(_ip));
_socket->blockSignals(true);
_socket->abort();
}
}