Pool Video Switch v2
Software video switch for distributed remote display in a lecture environment
client.cpp
Go to the documentation of this file.
1 /*
2  * client.cpp
3  *
4  * Created on: 18.01.2013
5  * Author: sr
6  */
7 
8 #include "client.h"
9 #include "../serverapp/serverapp.h"
10 #include "../../shared/settings.h"
11 #include "../../shared/util.h"
12 
13 #include <QPixmap>
14 #include <cassert>
15 #include <QNetworkInterface>
16 #include <QTcpSocket>
17 #include <QSslSocket>
18 #include <QTimer>
19 
20 #define CHALLENGE_LEN 20
21 
23 Client::Client(QTcpSocket* socket)
24  : _socket(socket)
25 {
26  assert(socket != nullptr);
28  _socket->setParent(nullptr);
30  //_ip = _socket->peerAddress().toString();
31  qDebug("*** Client %s created.", qPrintable(_socket->peerAddress().toString()));
32  // Connect important signals
33  connect(_socket, &QTcpSocket::disconnected,
34  [this]() {
35  this->disconnect("Client closed connection");
36  });
37  connect(_socket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::errorOccurred),
38  [this](QAbstractSocket::SocketError) {
39  this->disconnect("Client socket error");
40  });
41  auto *ssl = qobject_cast<QSslSocket*>(_socket);
42  if (ssl != nullptr) {
43  connect(ssl, QOverload<const QList<QSslError> &>::of(&QSslSocket::sslErrors),
44  [this](const QList<QSslError> &) {
45  this->disconnect("Client SSL Errors");
46  });
47  }
48  connect(_socket, &QTcpSocket::readyRead,
49  this, &Client::onDataArrival);
50  // Send challenge
51  _challenge.resize(CHALLENGE_LEN);
52  for (int i = 0; i < CHALLENGE_LEN; ++i) {
53  _challenge[i] = char(slxrand() & 0xff);
54  }
55  NetworkMessage msgChallenge;
56  msgChallenge.setField(_ID, _CHALLENGE);
57  msgChallenge.setField(_CHALLENGE, _challenge);
58  msgChallenge.writeMessage(_socket);
59  // give client 3 seconds to complete handshake
60  _timerIdAuthTimeout = startTimer(3000);
61  _timerPingTimeout = startTimer(3000);
62  _pingTimeout = QDateTime::currentMSecsSinceEpoch() + PING_TIMEOUT_MS;
63 }
64 
66 {
67  qDebug() << "*** Client" << _host << " destroyed.";
68  _socket->blockSignals(true);
69  QTcpSocket *sck = _socket;
70  QTimer::singleShot(10, [sck]() {
71  sck->deleteLater();
72  });
73 }
74 
75 void Client::timerEvent(QTimerEvent* event)
76 {
77  if (event->timerId() == _timerPingTimeout) {
78  if (_pingTimeout < QDateTime::currentMSecsSinceEpoch()) {
79  killTimer(_timerPingTimeout);
80  this->disconnect("Disconnecting client because of ping timeout");
81  }
82  } else if (event->timerId() == _timerIdAuthTimeout) {
83  // Client did not send login request within 3 seconds
84  killTimer(_timerIdAuthTimeout);
86  this->disconnect("Did not authenticate withing three seconds");
87  } else
88  killTimer(event->timerId());
89 }
90 
92 {
93  if (_socket->state() != QAbstractSocket::ConnectedState)
94  return;
95  message.writeMessage(_socket);
96  if (!message.writeComplete()) {
97  qCritical() << "SendMessage to client " << _name << "@" << _socket->peerAddress().toString() << " failed!";
98  }
99 }
100 
102 {
103  NetworkMessage msg;
104  _wantsAttention = false;
105  msg.setField(_ID, _ATTENTION);
106  msg.setField(_ENABLE, __FALSE);
107  sendMessage(msg);
108  emit stateChanged();
109 }
110 
111 void Client::requestThumb(const QSize& size)
112 {
113  if (_socket->state() != QAbstractSocket::ConnectedState) {
114  qDebug("requestThumb called in bad state");
115  return;
116  }
117  NetworkMessage msgTmb;
118  msgTmb.setField(_ID, _THUMB);
119  msgTmb.setField(_X, QString::number(size.width()));
120  msgTmb.setField(_Y, QString::number(size.height()));
121  msgTmb.writeMessage(_socket);
122 }
123 
125 {
126  //
127  if (_socket->state() != QAbstractSocket::ConnectedState) {
128  qDebug("dataArrival called in bad state");
129  return;
130  }
131 
132  while (_socket->bytesAvailable() > 0) {
133  int ret = _fromClient.readMessage(_socket); // let the message read data from socket
134  if (ret == NM_READ_FAILED) { // error parsing msg, disconnect client!
135  this->disconnect("Malformed message received from client.");
136  return;
137  }
138  if (ret == NM_READ_INCOMPLETE)
139  return;
140  if (_fromClient.readComplete()) { // message is complete
141  this->handleMsg();
142  _fromClient.reset();
143  }
144  }
145 }
146 
148 {
149  _pingTimeout = QDateTime::currentMSecsSinceEpoch() + PING_TIMEOUT_MS;
150  const QString &id = _fromClient.getFieldString(_ID);
151  if (id.isEmpty()) {
152  qDebug("Received message with empty ID field. ignored.");
153  return;
154  }
155 
156  if (_authed == 2) {
157  // Following messages are only valid of the client is already authenticated
158  if (id == _THUMB) {
159  QImage image;
161  /* size 0 means the client is in exam-mode and therefore doesn't send any thumbnail */
162  if (_rawRemoteScreen.size() > 0) {
163  if (!image.loadFromData(_rawRemoteScreen)) {
164  qDebug("Could not decode thumbnail image from client.");
165  return;
166  }
167  emit thumbUpdated(this, image);
168  }
169  } else if (id == _VNCSERVER) {
170  // Client tells about startup of vnc server
171  const int port = _fromClient.getFieldString("PORT").toInt();
172  if (port <= 0) {
173  if (_vncPort <= 0) {
174  qDebug() << "Starting VNC server on client" << _name << " (" << _socket->peerAddress().toString() << ":" << QString::number(_vncPort) << ") failed.";
175  } else {
176  qDebug() << "Client " << _name << " stopped its VNC server";
177  }
178  } else {
181  qDebug() << "Client " << _name << " started its VNC server";
182  }
183  _vncPort = port;
184  emit vncServerStateChange(this);
185  emit stateChanged();
186  } else if (id == _VNCCLIENT) {
187  // Client tells us that it started or stopped displaying a remote screen via VNC
188  const int projectionSource = _fromClient.getFieldString("CLIENTID").toInt();
189  if (_fromClient.getFieldString("ENABLED").toInt() != 0) {
190  qDebug() << "Client " << _name << " started its VNC client (watching " << projectionSource << ")";
191  _isActiveVncClient = true;
193  emit vncClientStateChange(this);
194  } else {
195  qDebug() << "Client " << _name << " stopped its VNC client (watched " << projectionSource << ")";
196  _isActiveVncClient = false;
197  emit vncClientStateChange(this);
198  }
199  emit stateChanged();
200  } else if (id == _ATTENTION) {
202  emit stateChanged();
203  }
204  return;
205  }
206 
207  // Not authed yet, only care about login requests
208  if (_authed == 1) {
209  if (id == _LOGIN) {
210  killTimer(_timerIdAuthTimeout);
212  ClientLogin request;
213  request.accept = true;
214  request.host = _fromClient.getFieldString("HOST");
215  request.name = _fromClient.getFieldString("NAME");
216  request.examMode = _fromClient.getFieldString(_EXAMMODE) == __TRUE;
217  request.ip = _socket->peerAddress().toString();
218 
219  qDebug() << "Login request by " << request.name << (request.examMode ? "(in exam mode)" : "");
220  // emit event, see if request is accepted
221  emit authenticating(this, &request);
222  if (!request.accept) {
223  this->disconnect("Login request denied."); // Nope
224  return;
225  }
226  // Accepted
227  _authed = 2;
228  qDebug("valid, step <- 2");
229  _name = request.name;
230  _host = request.host;
231  NetworkMessage msgLogin;
232  msgLogin.setField(_ID, _LOGIN);
233  msgLogin.writeMessage(_socket);
234  emit authenticated(this);
235  }
236  return;
237  }
238 
239  // Did not pass challenge yet
240  if (_authed == 0) {
241  // Waiting for challenge reply by client
242  if (id == _CHALLENGE) {
243  QByteArray hash(_fromClient.getFieldBytes(_HASH));
244  QByteArray challenge(_fromClient.getFieldBytes(_CHALLENGE));
245  if (genSha1(&serverApp->sessionNameArray(), &_challenge) != hash
246  && !(serverApp->getCurrentRoom()->clientPositions.contains(_socket->peerAddress().toString()))) {
247  // Challenge reply is invalid, drop client
248  this->disconnect("Challenge reply invalid.");
249  return;
250  }
251  // Now answer to challenge by client
252  NetworkMessage msgChlng;
253  msgChlng.setField(_ID, _CHALLENGE);
254  msgChlng.setField(_HASH, genSha1(&serverApp->sessionNameArray(), &challenge));
255  msgChlng.writeMessage(_socket);
256  _authed = 1;
257  qDebug("client's challenge reply was valid, step <- 1");
258  }
259  return;
260  }
261 
262 }
263 
265 {
266  NetworkMessage msg;
267  msg.setField(_ID, _VNCSERVER);
268  msg.setField(_ENABLE, __TRUE);
269  sendMessage(msg);
270 }
271 
273 {
274  NetworkMessage msg;
275  msg.setField(_ID, _VNCSERVER);
276  msg.setField(_ENABLE, __FALSE);
277  sendMessage(msg);
278 }
279 
280 void Client::startVncClient(const Client * const to)
281 {
283  return; // Already watching given target, do nothing
284  NetworkMessage msg;
285  msg.setField(_ID, _VNCCLIENT);
286  msg.setField("HOST", to->_socket->peerAddress().toString());
287  msg.setField(_PORT, QString::number(to->_vncPort));
288  msg.setField("ROPASS", to->_vncRoPass);
289  msg.setField("CLIENTID", QString::number(to->_id));
290  msg.setField("CAPTION", to->_name + " @ " + to->_host);
291  if (!to->_rawRemoteScreen.isEmpty()) {
292  msg.setField(_THUMB, to->_rawRemoteScreen);
293  }
294  sendMessage(msg);
295 }
296 
298 {
299  if (_isActiveVncClient) {
300  NetworkMessage msg;
301  msg.setField(_ID, _VNCCLIENT);
302  sendMessage(msg);
303  }
305 }
306 
312 {
313  foreach (const QHostAddress & address, QNetworkInterface::allAddresses())
314  if (address != QHostAddress(QHostAddress::LocalHost)
315  && this->ip() == address.toString())
316  return true;
317  return false;
318 }
319 
321 {
322  if (_isTutor || isManagerMachine()) {
323  lock = false;
324  }
325  if (_locked != lock) {
326  _locked = lock;
327  NetworkMessage msg;
328  msg.setField(_ID, _LOCK);
329  msg.setField(_ENABLE, _BOOL(lock));
330  sendMessage(msg);
331  }
332  emit stateChanged();
333 }
334 
335 void Client::disconnect(const char *errmsg)
336 {
337  qDebug() << "*** Client" << _socket->peerAddress().toString() << "disconnected:" << errmsg;
338  if (_socket->state() == QAbstractSocket::ConnectedState) {
339  NetworkMessage msgErr;
340  msgErr.buildErrorMessage(errmsg);
341  msgErr.writeMessage(_socket);
342  _socket->flush();
343  }
344  _socket->blockSignals(true);
345  this->deleteLater();
346  emit disconnected();
347 }
348 
349 QString Client::ip() const
350 {
351  return _socket->peerAddress().toString();
352 }
QString host
Definition: client.h:17
void authenticating(Client *client, ClientLogin *request)
void authenticated(Client *client)
static const QByteArray __TRUE("1")
static const QByteArray __FALSE("0")
QString ip() const
Definition: client.cpp:349
#define NM_READ_INCOMPLETE
#define serverApp
Definition: serverapp.h:31
void stateChanged()
QString ip
Definition: client.h:18
bool writeMessage(QAbstractSocket *socket)
NetworkMessage _fromClient
Definition: client.h:68
void timerEvent(QTimerEvent *event) override
Definition: client.cpp:75
QString getFieldString(const QByteArray &key) const
bool _wantsAttention
Definition: client.h:78
static quint16 hash(const QHostAddress &host)
hash
bool readComplete() const
qint64 _pingTimeout
Definition: client.h:67
#define PING_TIMEOUT_MS
Definition: settings.h:12
QString _host
Definition: client.h:65
void disconnected()
~Client() override
Definition: client.cpp:65
int _projectionSource
Definition: client.h:74
QByteArray genSha1(const QByteArray *a, const QByteArray *b, const QByteArray *c, const QByteArray *d, const QByteArray *e)
Definition: util.cpp:13
void handleMsg()
Definition: client.cpp:147
void startVncServer()
Definition: client.cpp:264
QTcpSocket *const _socket
Definition: client.h:61
#define NO_SOURCE
Definition: client.h:12
void disconnect(const char *errmsg)
Definition: client.cpp:335
bool examMode
Definition: client.h:19
bool _isActiveVncClient
Definition: client.h:75
#define slxrand()
Definition: util.h:19
void startVncClient(const Client *to)
Definition: client.cpp:280
void stopVncServer()
Definition: client.cpp:272
QByteArray _challenge
Definition: client.h:66
int _timerIdAuthTimeout
Definition: client.h:69
void sendMessage(NetworkMessage &message)
Definition: client.cpp:91
Definition: client.h:23
int readMessage(QAbstractSocket *socket)
void thumbUpdated(Client *client, const QImage &thumb)
void removeAttentionInternal()
Definition: client.cpp:101
int projectionSource() const
Definition: client.h:41
bool writeComplete() const
void requestThumb(const QSize &size)
Definition: client.cpp:111
QString _vncRoPass
Definition: client.h:71
Client(QTcpSocket *socket)
Definition: client.cpp:23
bool isManagerMachine() const
Checks if client and manager runs on same machine.
Definition: client.cpp:311
bool accept
Definition: client.h:15
void vncClientStateChange(Client *client)
bool _isTutor
Definition: client.h:76
QString _vncRwPass
Definition: client.h:71
#define NM_READ_FAILED
QString _name
Definition: client.h:64
void vncServerStateChange(Client *client)
QString name
Definition: client.h:16
static const QByteArray & _BOOL(bool val)
void lockScreen(bool)
Definition: client.cpp:320
static int _clientIdCounter
Definition: client.h:82
void stopVncClient()
Definition: client.cpp:297
static QIcon * lock
bool _locked
Definition: client.h:62
QByteArray _rawRemoteScreen
Definition: client.h:79
QByteArray getFieldBytes(const QByteArray &key) const
void setField(const QByteArray &key, const QByteArray &value)
int _vncPort
Definition: client.h:72
int _id
Definition: client.h:70
void onDataArrival()
Definition: client.cpp:124
int _authed
Definition: client.h:63
int _desiredSource
Definition: client.h:73
int _timerPingTimeout
Definition: client.h:69
#define CHALLENGE_LEN
Definition: client.cpp:20
void buildErrorMessage(const QString &error)