/*
* 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 <QPixmap>
#include <cassert>
#define CHALLENGE_LEN 20
int Client::_clientIdCounter = 0;
/******************************************************************************/
Client::Client(QSslSocket* socket) : _socket(socket)
{
assert(socket != NULL);
_authed = 0;
_currentProjectionSource = 0;
_vncPort = 0;
_isTutor = false;
_locked = false;
_id = ++_clientIdCounter;
//_ip = _socket->peerAddress().toString();
qDebug("*** Client %s created.", qPrintable(_socket->peerAddress().toString()));
// Connect important signals
connect(_socket, SIGNAL(disconnected()),
this, SLOT(disconnect()));
connect(_socket, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(disconnect()));
connect(_socket, SIGNAL(sslErrors(const QList<QSslError> &)),
this, SLOT(disconnect()));
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;
NetworkMessage msgChlng;
msgChlng.setField(_ID, _CHALLENGE);
msgChlng.setField(_CHALLENGE, _challenge);
msgChlng.writeMessage(_socket);
// give client 3 seconds to complete handshake
_timerIdAuthTimeout = startTimer(600000);// TODO undo, for debugging purposes 10min
_timerPingTimeout = startTimer(600000); // TODO undo, for debugging purposes 10min
_pingTimeout = QDateTime::currentMSecsSinceEpoch() + PING_TIMEOUT_MS;
}
/******************************************************************************/
Client::~Client()
{
qDebug() << "*** Client" << _host << " destroyed.";
_socket->deleteLater();
}
/******************************************************************************/
void Client::timerEvent(QTimerEvent* event)
{
if (event->timerId() == _timerPingTimeout)
{
if (_pingTimeout < QDateTime::currentMSecsSinceEpoch())
{
qDebug() << "Client" << _socket->peerAddress().toString() << "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
killTimer(event->timerId());
}
/******************************************************************************/
void Client::sendMessage(NetworkMessage& message)
{
if (_socket->state() != QAbstractSocket::ConnectedState)
return;
message.writeMessage(_socket);
if (!message.writeComplete())
{
qCritical() << "SendMessage to client " << _name << "@" << _socket->peerAddress().toString() << " failed!";
}
}
/******************************************************************************/
void Client::requestThumb(const int width, const int height)
{
if (_socket->state() != QAbstractSocket::ConnectedState)
{
qDebug("requestThumb called in bad state");
return;
}
NetworkMessage msgTmb;
msgTmb.setField(_ID, _THUMB);
msgTmb.setField(_X, QString::number(width));
msgTmb.setField(_Y, QString::number(height));
msgTmb.writeMessage(_socket);
}
/******************************************************************************/
void Client::onDataArrival()
{
//
if (_socket->state() != QAbstractSocket::ConnectedState)
{
qDebug("dataArrival called in bad state");
return;
}
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();
}
}
}
/******************************************************************************/
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 << " (" << _socket->peerAddress().toString()+_vncPort << ") failed.";
}
else
{
qDebug() << "Client " << _name << " stopped its VNC server";
}
}
else
{
_vncRoPass = _fromClient.getFieldString("ROPASS");
_vncRwPass = _fromClient.getFieldString("RWPASS");
qDebug() << "Client " << _name << " started its VNC server";
}
_vncPort = port;
emit vncServerStateChange(this);
emit stateChanged();
}
else if (id == _VNCCLIENT)
{
// Client tells us that it started or stopped displaying a remote screen via VNC
const int projectionSource = (int)_fromClient.getFieldString("CLIENTID").toInt();
if (_fromClient.getFieldString("ENABLED").toInt() != 0)
{
qDebug() << "Client " << _name << " started its VNC client (watching " << projectionSource << ")";
_currentProjectionSource = projectionSource;
emit vncClientStateChange(this);
}
else
{
qDebug() << "Client " << _name << " stopped its VNC client (watched " << projectionSource << ")";
_currentProjectionSource = 0;
emit vncClientStateChange(this);
}
emit stateChanged();
}
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;
NetworkMessage msgLogin;
msgLogin.setField(_ID, _LOGIN);
msgLogin.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
NetworkMessage msgErr;
msgErr.buildErrorMessage("Challenge reply invalid.");
msgErr.writeMessage(_socket);
this->disconnect();
return;
}
// Now answer to challenge by client
NetworkMessage msgChlng;
msgChlng.setField(_ID, _CHALLENGE);
msgChlng.setField(_HASH, genSha1(&Global::sessionNameArray(), &challenge));
msgChlng.writeMessage(_socket);
_authed = 1;
qDebug("client's challenge reply was valid, step <- 1");
}
return;
}
}
/******************************************************************************/
void Client::startVncServer()
{
NetworkMessage msg;
msg.setField(_ID, _VNCSERVER);
msg.setField(_ENABLE, __TRUE);
sendMessage(msg);
}
/******************************************************************************/
void Client::stopVncServer()
{
NetworkMessage msg;
msg.setField(_ID, _VNCSERVER);
msg.setField(_ENABLE, __FALSE);
sendMessage(msg);
}
/******************************************************************************/
void Client::startVncClient(const Client * const to)
{
NetworkMessage msg;
msg.setField(_ID, _VNCCLIENT);
msg.setField("HOST", to->_socket->peerAddress().toString());
msg.setField("PORT", QString::number(to->_vncPort));
msg.setField("ROPASS", to->_vncRoPass);
msg.setField("CLIENTID", QString::number(to->_id));
msg.setField("CAPTION", to->_name + " @ " + to->_host);
sendMessage(msg);
}
/******************************************************************************/
void Client::stopVncClient()
{
if (_currentProjectionSource != 0) {
NetworkMessage msg;
msg.setField(_ID, _VNCCLIENT);
sendMessage(msg);
}
}
/******************************************************************************/
void Client::lockScreen(bool lock)
{
if (!_isTutor && _locked != lock){
_locked = lock;
NetworkMessage msg;
msg.setField(_ID, _LOCK);
msg.setField(_ENABLE, lock ? __TRUE : __FALSE);
sendMessage(msg);
}
emit stateChanged();
}
/******************************************************************************/
void Client::disconnect()
{
qDebug("*** Client %s disconnected.", qPrintable(_socket->peerAddress().toString()));
_socket->blockSignals(true);
_socket->abort();
this->deleteLater();
emit disconnected();
}