/*
* client.cpp
*
* Created on: 18.01.2013
* Author: sr
*/
#include "client.h"
#include "../serverapp/serverapp.h"
#include "../../shared/settings.h"
#include "../../shared/util.h"
#include <QPixmap>
#include <cassert>
#include <QNetworkInterface>
#define CHALLENGE_LEN 20
int Client::_clientIdCounter = 0;
/******************************************************************************/
Client::Client(QSslSocket* socket) : _socket(socket)
{
assert(socket != NULL);
_authed = 0;
_projectionSource = 0;
_desiredSource = 0;
_isActiveVncClient = false;
_vncPort = 0;
_isTutor = false;
_locked = false;
_wantsAttention = 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(3000);
_timerPingTimeout = startTimer(3000);
_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::removeAttentionInternal()
{
NetworkMessage msg;
_wantsAttention = false;
msg.setField(_ID, _ATTENTION);
msg.setField(_ENABLE, __FALSE);
sendMessage(msg);
emit stateChanged();
}
/******************************************************************************/
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;
}
while (_socket->bytesAvailable() > 0) {
int ret = _fromClient.readMessage(_socket); // let the message read data from socket
if (ret == NM_READ_FAILED) { // error parsing msg, disconnect client!
this->disconnect();
return;
}
if (ret == NM_READ_INCOMPLETE)
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;
}
if (_authed == 2) {
// Following messages are only valid of the client is already authenticated
if (id == _THUMB) {
QPixmap pixmap;
const QByteArray& rawImage = _fromClient.getFieldBytes("IMG");
/* size 0 means the client is in exam-mode and therefore doesn't send any thumbnail */
if (rawImage.size() > 0) {
if (!pixmap.loadFromData(rawImage)) {
qDebug("Could not decode thumbnail image from client.");
return;
}
_rawRemoteScreen = QByteArray(rawImage);
emit thumbUpdated(this, pixmap, rawImage);
}
} 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 << ")";
_isActiveVncClient = true;
emit vncClientStateChange(this);
} else {
qDebug() << "Client " << _name << " stopped its VNC client (watched " << projectionSource << ")";
_isActiveVncClient = false;
emit vncClientStateChange(this);
}
emit stateChanged();
} else if (id == _ATTENTION) {
_wantsAttention = _fromClient.getFieldString(_ENABLE) == __TRUE;
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.examMode = _fromClient.getFieldString(_EXAMMODE) == __TRUE;
request.ip = _socket->peerAddress().toString();
qDebug() << "Login request by " << request.name << (request.examMode ? "(in exam mode)" : "");
// 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(&serverApp->sessionNameArray(), &_challenge) != hash
&& !(serverApp->getCurrentRoom()->clientPositions.contains(_socket->peerAddress().toString()))) {
// 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(&serverApp->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);
if (!to->_rawRemoteScreen.isEmpty()) {
msg.setField(_THUMB, to->_rawRemoteScreen);
}
sendMessage(msg);
}
/******************************************************************************/
void Client::stopVncClient()
{
if (_isActiveVncClient) {
NetworkMessage msg;
msg.setField(_ID, _VNCCLIENT);
sendMessage(msg);
}
}
/***************************************************************************//**
* Checks if client and manager runs on same machine.
* @return Return true, if pvsmanager is running on client.
*/
bool Client::isManagerMachine()
{
foreach (const QHostAddress & address, QNetworkInterface::allAddresses())
if (address != QHostAddress(QHostAddress::LocalHost)
&& this->ip() == address.toString())
return true;
return false;
}
/******************************************************************************/
void Client::lockScreen(bool lock)
{
if (!_isTutor && _locked != lock && !isManagerMachine()) {
_locked = lock;
NetworkMessage msg;
msg.setField(_ID, _LOCK);
msg.setField(_ENABLE, _BOOL(lock));
sendMessage(msg);
}
emit stateChanged();
}
/******************************************************************************/
void Client::disconnect()
{
qDebug("*** Client %s disconnected.", qPrintable(_socket->peerAddress().toString()));
_socket->blockSignals(true);
_socket->abort();
this->deleteLater();
emit disconnected();
}