MythTV  master
websocket.cpp
Go to the documentation of this file.
1 #include <chrono>
2 
3 // Own header
4 #include "websocket.h"
5 
6 // MythTV headers
7 #include "mythlogging.h"
8 #include "mythcorecontext.h"
9 #include "mythevent.h"
10 #include "codecutil.h"
12 
13 // QT headers
14 #include <QCryptographicHash>
15 #include <QSslCipher>
16 #include <QTcpSocket>
17 #include <QThread>
18 #include <QtCore>
19 #include <QtGlobal>
20 
22 //
24 
26  m_threadPool("WebSocketServerPool")
27 {
28  setObjectName("WebSocketServer");
29  // Number of connections processed concurrently
30  int maxThreads = qMax(QThread::idealThreadCount() * 2, 2); // idealThreadCount can return -1
31  // Don't allow more connections than we can process, it will simply cause
32  // browsers to stall
33  setMaxPendingConnections(maxThreads);
34  m_threadPool.setMaxThreadCount(maxThreads);
35 
36  LOG(VB_HTTP, LOG_NOTICE, QString("WebSocketServer(): Max Thread Count %1")
38 }
39 
41 {
42  m_rwlock.lockForWrite();
43  m_running = false;
44  m_rwlock.unlock();
45 
47 }
48 
50 {
51 
53  auto *server = qobject_cast<PrivTcpServer *>(QObject::sender());
54  if (server)
55  type = server->GetServerType();
56 
58  new WebSocketWorkerThread(*this, socket, type
59 #ifndef QT_NO_OPENSSL
60  , m_sslConfig
61 #endif
62  ),
63  QString("WebSocketServer%1").arg(socket));
64 }
65 
67 //
69 
72 #ifndef QT_NO_OPENSSL
73  , const QSslConfiguration& sslConfig
74 #endif
75  )
76  :
77  m_webSocketServer(webSocketServer), m_socketFD(sock),
78  m_connectionType(type)
79 #ifndef QT_NO_OPENSSL
80  , m_sslConfig(sslConfig)
81 #endif
82 {
83 }
84 
86 {
87  auto *worker = new WebSocketWorker(m_webSocketServer, m_socketFD,
89 #ifndef QT_NO_OPENSSL
90  , m_sslConfig
91 #endif
92  );
93  worker->Exec();
94 
95  worker->deleteLater();
96 }
97 
99 //
101 
104 #ifndef QT_NO_OPENSSL
105  , const QSslConfiguration& sslConfig
106 #endif
107  )
108  : m_eventLoop(new QEventLoop()),
109  m_webSocketServer(webSocketServer),
110  m_socketFD(sock), m_connectionType(type),
111  m_heartBeat(new QTimer())
112 #ifndef QT_NO_OPENSSL
113  , m_sslConfig(sslConfig)
114 #endif
115 {
116  setObjectName(QString("WebSocketWorker(%1)")
117  .arg(m_socketFD));
118  LOG(VB_HTTP, LOG_INFO, QString("WebSocketWorker(%1): New connection")
119  .arg(m_socketFD));
120 
121  // For now, until it's refactored, register the only extension here
123 
124  SetupSocket();
125 
126  m_isRunning = true;
127 }
128 
130 {
131  CleanupSocket();
132 
133  // If extensions weren't deregistered then it's our responsibility to
134  // delete them
135  while (!m_extensions.isEmpty())
136  {
137  WebSocketExtension *extension = m_extensions.takeFirst();
138  extension->deleteLater();
139  extension = nullptr;
140  }
141 
142  m_eventLoop->deleteLater();
143  m_eventLoop = nullptr;
144  delete m_heartBeat;
145 }
146 
148 {
149  m_eventLoop->exec();
150 }
151 
153 {
154  blockSignals(true);
155  m_isRunning = false;
156  LOG(VB_HTTP, LOG_INFO, "WebSocketWorker::CloseConnection()");
157  if (m_eventLoop)
158  m_eventLoop->exit();
159 }
160 
162 {
164  {
165 
166 #ifndef QT_NO_OPENSSL
167  auto *pSslSocket = new QSslSocket();
168  if (pSslSocket->setSocketDescriptor(m_socketFD)
169  && gCoreContext->CheckSubnet(pSslSocket))
170  {
171  pSslSocket->setSslConfiguration(m_sslConfig);
172  pSslSocket->startServerEncryption();
173  if (pSslSocket->waitForEncrypted(5000))
174  {
175  LOG(VB_HTTP, LOG_INFO, "SSL Handshake occurred, connection encrypted");
176  LOG(VB_HTTP, LOG_INFO, QString("Using %1 cipher").arg(pSslSocket->sessionCipher().name()));
177  }
178  else
179  {
180  LOG(VB_HTTP, LOG_WARNING, "SSL Handshake FAILED, connection terminated");
181  delete pSslSocket;
182  pSslSocket = nullptr;
183  }
184  }
185  else
186  {
187  delete pSslSocket;
188  pSslSocket = nullptr;
189  }
190 
191  if (pSslSocket)
192  m_socket = pSslSocket;
193  else
194  return;
195 #else
196  return;
197 #endif
198  }
199  else // Plain old unencrypted socket
200  {
201  m_socket = new QTcpSocket();
202  m_socket->setSocketDescriptor(m_socketFD);
204  {
205  delete m_socket;
206  m_socket = nullptr;
207  return;
208  }
209 
210  }
211 
212  m_socket->setSocketOption(QAbstractSocket::KeepAliveOption, QVariant(1));
213 
214  connect(m_socket, &QIODevice::readyRead, this, &WebSocketWorker::doRead);
215  connect(m_socket, &QAbstractSocket::disconnected, this, &WebSocketWorker::CloseConnection);
216 
217  // Setup heartbeat
218  m_heartBeat->setInterval(20s);
219  m_heartBeat->setSingleShot(false);
221 }
222 
224 {
225  if (m_socket->error() != QAbstractSocket::UnknownSocketError)
226  {
227  LOG(VB_HTTP, LOG_WARNING, QString("WebSocketWorker(%1): Error %2 (%3)")
228  .arg(m_socketFD)
229  .arg(m_socket->errorString())
230  .arg(m_socket->error()));
231  }
232 
233  std::chrono::milliseconds writeTimeout = 5s;
234  // Make sure any data in the buffer is flushed before the socket is closed
235  while (m_webSocketServer.IsRunning() &&
236  m_socket->isValid() &&
237  m_socket->state() == QAbstractSocket::ConnectedState &&
238  m_socket->bytesToWrite() > 0)
239  {
240  LOG(VB_HTTP, LOG_DEBUG, QString("WebSocketWorker(%1): "
241  "Waiting for %2 bytes to be written "
242  "before closing the connection.")
243  .arg(m_socketFD)
244  .arg(m_socket->bytesToWrite()));
245 
246  // If the client stops reading for longer than 'writeTimeout' then
247  // stop waiting for them. We can't afford to leave the socket
248  // connected indefinitely, it could be used by another client.
249  //
250  // NOTE: Some clients deliberately stall as a way of 'pausing' A/V
251  // streaming. We should create a new server extension or adjust the
252  // timeout according to the User-Agent, instead of increasing the
253  // standard timeout. However we should ALWAYS have a timeout.
254  if (!m_socket->waitForBytesWritten(writeTimeout.count()))
255  {
256  LOG(VB_GENERAL, LOG_WARNING, QString("WebSocketWorker(%1): "
257  "Timed out waiting to write bytes to "
258  "the socket, waited %2 seconds")
259  .arg(m_socketFD)
260  .arg(writeTimeout.count() / 1000));
261  break;
262  }
263  }
264 
265  if (m_socket->bytesToWrite() > 0)
266  {
267  LOG(VB_HTTP, LOG_WARNING, QString("WebSocketWorker(%1): "
268  "Failed to write %2 bytes to "
269  "socket, (%3)")
270  .arg(m_socketFD)
271  .arg(m_socket->bytesToWrite())
272  .arg(m_socket->errorString()));
273  }
274 
275  LOG(VB_HTTP, LOG_INFO, QString("WebSocketWorker(%1): Connection %2 closed.")
276  .arg(m_socketFD)
277  .arg(m_socket->socketDescriptor()));
278 
279  m_socket->close();
280  m_socket->deleteLater();
281  m_socket = nullptr;
282 }
283 
285 {
286  if (!m_isRunning)
287  return;
288 
289  if (!m_webSocketServer.IsRunning() || !m_socket->isOpen())
290  {
291  CloseConnection();
292  return;
293  }
294 
295  if (m_webSocketMode)
296  {
298  }
299  else
300  {
303  }
304 
305  if (!m_webSocketMode)
306  {
307  LOG(VB_HTTP, LOG_WARNING, "WebSocketServer: Timed out waiting for connection upgrade");
308  CloseConnection();
309  }
310 }
311 
312 bool WebSocketWorker::ProcessHandshake(QTcpSocket *socket)
313 {
314  QByteArray line;
315 
316  // Minimum length of the request line is 16
317  // i.e. "GET / HTTP/1.1\r\n"
318  while (socket->bytesAvailable() < 16)
319  {
320  if (!socket->waitForReadyRead(5000)) // 5 second timeout
321  return false;
322  }
323 
324  // Set a maximum length for a request line of 255 bytes, it's an
325  // arbitrary limit but a reasonable one for now
326  line = socket->readLine(255).trimmed(); // Strip newline
327  if (line.isEmpty())
328  return false;
329 
330 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
331  QStringList tokens = QString(line).split(' ', QString::SkipEmptyParts);
332 #else
333  QStringList tokens = QString(line).split(' ', Qt::SkipEmptyParts);
334 #endif
335 
336  if (tokens.length() != 3) // Anything but 3 is invalid - {METHOD} {HOST/PATH} {PROTOCOL}
337  {
338  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::ProcessHandshake() - Invalid number of tokens in Request line");
339  return false;
340  }
341 
342  if (tokens[0] != "GET") // RFC 6455 - Request method MUST be GET
343  {
344  LOG(VB_GENERAL, LOG_ERR, QString("WebSocketWorker::ProcessHandshake() - Invalid method: %1").arg(tokens[0]));
345  return false;
346  }
347 
348  QString path = tokens[1];
349 
350  if (path.contains('#')) // RFC 6455 - Fragments MUST NOT be used
351  {
352  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::ProcessHandshake() - Path contains illegal fragment");
353  return false;
354  }
355 
356  if (!tokens[2].startsWith("HTTP/")) // Must be HTTP
357  {
358  LOG(VB_GENERAL, LOG_ERR, QString("WebSocketWorker::ProcessHandshake() - Invalid protocol: %1").arg(tokens[2]));
359  return false;
360  }
361 
362  QString versionStr = tokens[2].section('/', 1, 1);
363 
364  int majorVersion = versionStr.section('.', 0, 0).toInt();
365  int minorVersion = versionStr.section('.', 1, 1).toInt();
366 
367  if ((majorVersion < 1) || (minorVersion < 1)) // RFC 6455 - HTTP version MUST be at least 1.1
368  {
369  LOG(VB_GENERAL, LOG_ERR, QString("WebSocketWorker::ProcessHandshake() - Invalid HTTP version: %1.%2").arg(majorVersion).arg(minorVersion));
370  return false;
371  }
372 
373  // Read Header
374  line = socket->readLine(2000).trimmed(); // 2KB Maximum
375 
376  QMap<QString, QString> requestHeaders;
377  while (!line.isEmpty())
378  {
379  QString strLine = QString(line);
380  QString sName = strLine.section( ':', 0, 0 ).trimmed();
381  QString sValue = strLine.section( ':', 1 ).trimmed();
382  sValue.replace(" ",""); // Remove all internal whitespace
383 
384  if (!sName.isEmpty() && !sValue.isEmpty())
385  requestHeaders.insert(sName.toLower(), sValue);
386 
387  line = socket->readLine(2000).trimmed(); // 2KB Maximum
388  }
389 
390  // Dump request header
391  QMap<QString, QString>::iterator it;
392  for ( it = requestHeaders.begin();
393  it != requestHeaders.end();
394  ++it )
395  {
396  LOG(VB_HTTP, LOG_INFO, QString("(Request Header) %1: %2")
397  .arg(it.key()).arg(*it));
398  }
399 
400  if (!requestHeaders.contains("connection")) // RFC 6455 - 1.3. Opening Handshake
401  {
402  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::ProcessHandshake() - Missing 'Connection' header");
403  return false;
404  }
405 
406 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
407  QStringList connectionValues = requestHeaders["connection"].split(',', QString::SkipEmptyParts);
408 #else
409  QStringList connectionValues = requestHeaders["connection"].split(',', Qt::SkipEmptyParts);
410 #endif
411  if (!connectionValues.contains("Upgrade", Qt::CaseInsensitive)) // RFC 6455 - 1.3. Opening Handshake
412  {
413  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::ProcessHandshake() - Invalid 'Connection' header");
414  return false;
415  }
416 
417  if (!requestHeaders.contains("upgrade") ||
418  requestHeaders["upgrade"].toLower() != "websocket") // RFC 6455 - 1.3. Opening Handshake
419  {
420  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::ProcessHandshake() - Missing or invalid 'Upgrade' header");
421  return false;
422  }
423 
424  if (!requestHeaders.contains("sec-websocket-version")) // RFC 6455 - 1.3. Opening Handshake
425  {
426  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::ProcessHandshake() - Missing 'Sec-WebSocket-Version' header");
427  return false;
428  }
429 
430  if (requestHeaders["sec-websocket-version"] != "13") // RFC 6455 - 1.3. Opening Handshake
431  {
432  LOG(VB_GENERAL, LOG_ERR, QString("WebSocketWorker::ProcessHandshake() - Unsupported WebSocket protocol "
433  "version. We speak '13' the client speaks '%1'")
434  .arg(requestHeaders["sec-websocket-version"]));
435  return false;
436  }
437 
438  if (!requestHeaders.contains("sec-websocket-key") ||
439  QByteArray::fromBase64(requestHeaders["sec-websocket-key"].toLatin1()).length() != 16) // RFC 6455 - 1.3. Opening Handshake
440  {
441  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::ProcessHandshake() - Missing or invalid 'sec-websocket-key' header");
442  return false;
443  }
444 
445  // If we're running the Autobahn fuzz/unit tester then we need to disable
446  // the heartbeat and 'echo' text/binary frames back to the sender
447  if (requestHeaders.contains("user-agent") &&
448  requestHeaders["user-agent"].contains("AutobahnTestSuite"))
449  m_fuzzTesting = true;
450 
451  QString key = requestHeaders["sec-websocket-key"];
452  const QString magicString = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
453 
454  // Response token - Watch very closely, there's a lot happening here
455  QString acceptToken = QCryptographicHash::hash(key.append(magicString).toUtf8(),
456  QCryptographicHash::Sha1).toBase64();
457 
458  QMap<QString, QString> responseHeaders;
459  responseHeaders.insert("Connection", "Upgrade");
460  responseHeaders.insert("Upgrade", "websocket");
461  responseHeaders.insert("Sec-WebSocket-Accept", acceptToken);
462 
463  socket->write("HTTP/1.1 101 Switching Protocols\r\n");
464 
465  QString header("%1: %2\r\n");
466  for (it = responseHeaders.begin(); it != responseHeaders.end(); ++it)
467  {
468  socket->write(header.arg(it.key()).arg(*it).toLatin1().constData());
469  LOG(VB_HTTP, LOG_INFO, QString("(Response Header) %1: %2")
470  .arg(it.key()).arg(*it));
471  }
472 
473  socket->write("\r\n");
474  socket->waitForBytesWritten();
475 
476  m_webSocketMode = true;
477  // Start the heart beat
478  if (!m_fuzzTesting)
479  m_heartBeat->start();
480 
481  return true;
482 }
483 
484 void WebSocketWorker::ProcessFrames(QTcpSocket *socket)
485 {
486  while (m_isRunning && socket && socket->bytesAvailable() >= 2) // No header? Return and wait for more
487  {
488  uint8_t headerSize = 2; // Smallest possible header size is 2 bytes, greatest is 14 bytes
489 
490  QByteArray header = socket->peek(headerSize); // Read header to establish validity and size of frame
491 
492  WebSocketFrame frame;
493  // FIN
494  frame.m_finalFrame = (bool)(header[0] & 0x80);
495  // Reserved bits
496  if (header.at(0) & 0x70)
497  {
498  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::ProcessFrames() "
499  "- Invalid data in reserved fields");
500  SendClose(kCloseProtocolError, "Invalid data in reserved fields");
501  return;
502  }
503  // Operation code
504  uint8_t opCode = (header.at(0) & 0xF);
505  if ((opCode > WebSocketFrame::kOpBinaryFrame &&
506  opCode < WebSocketFrame::kOpClose) ||
507  (opCode > WebSocketFrame::kOpPong))
508  {
509  LOG(VB_GENERAL, LOG_ERR, QString("WebSocketWorker::ProcessFrames() "
510  "- Invalid OpCode (%1)")
511  .arg(QString::number(opCode, 16)));
512  SendClose(kCloseProtocolError, "Invalid OpCode");
513  return;
514  }
515  frame.m_opCode = (WebSocketFrame::OpCode)opCode;
516  frame.m_isMasked = (((header.at(1) >> 7) & 0xFE) != 0);
517 
518  if (frame.m_isMasked)
519  headerSize += 4; // Add 4 bytes for the mask
520 
521  frame.m_payloadSize = (header.at(1) & 0x7F);
522  // Handle 16 or 64bit payload size headers
523  if (frame.m_payloadSize >= 126)
524  {
525  uint8_t payloadHeaderSize = 2; // 16bit payload size
526  if (frame.m_payloadSize == 127)
527  payloadHeaderSize = 8; // 64bit payload size
528 
529  headerSize += payloadHeaderSize; // Add bytes for extended payload size
530 
531  if (socket->bytesAvailable() < headerSize)
532  return; // Return and wait for more
533 
534  header = socket->peek(headerSize); // Peek the entire header
535  QByteArray payloadHeader = header.mid(2,payloadHeaderSize);
536  frame.m_payloadSize = 0;
537  for (int i = 0; i < payloadHeaderSize; i++)
538  {
539  frame.m_payloadSize |= ((uint8_t)payloadHeader.at(i) << ((payloadHeaderSize - (i + 1)) * 8));
540  }
541  }
542  else
543  {
544  if (socket->bytesAvailable() < headerSize)
545  return; // Return and wait for more
546  header = socket->peek(headerSize); // Peek the entire header including mask
547  }
548 
549  while ((uint64_t)socket->bytesAvailable() < (frame.m_payloadSize + header.length()))
550  {
551  if (!socket->waitForReadyRead(2000)) // Wait 2 seconds for the next chunk of the frame
552  {
553 
554  m_errorCount++;
555 
556  if (m_errorCount == 5)
557  {
558  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::ProcessFrames() - Timed out waiting for rest of frame to arrive.");
560  }
561  return;
562  }
563  }
564 
567 
568  LOG(VB_HTTP, LOG_DEBUG, QString("Read Header: %1").arg(QString(header.toHex())));
569  LOG(VB_HTTP, LOG_DEBUG, QString("Final Frame: %1").arg(frame.m_finalFrame ? "Yes" : "No"));
570  LOG(VB_HTTP, LOG_DEBUG, QString("Op Code: %1").arg(QString::number(frame.m_opCode)));
571  LOG(VB_HTTP, LOG_DEBUG, QString("Payload Size: %1 Bytes").arg(QString::number(frame.m_payloadSize)));
572  LOG(VB_HTTP, LOG_DEBUG, QString("Total Payload Size: %1 Bytes").arg(QString::number( m_readFrame.m_payloadSize)));
573 
574  if (!m_fuzzTesting &&
575  frame.m_payloadSize > qPow(2,20)) // Set 1MB limit on payload per frame
576  {
577  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::ProcessFrames() - Frame payload larger than limit of 1MB");
578  SendClose(kCloseTooLarge, "Frame payload larger than limit of 1MB");
579  return;
580  }
581 
582  if (!m_fuzzTesting &&
583  m_readFrame.m_payloadSize > qPow(2,22)) // Set 4MB limit on total payload
584  {
585  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::ProcessFrames() - Total payload larger than limit of 4MB");
586  SendClose(kCloseTooLarge, "Total payload larger than limit of 4MB");
587  return;
588  }
589 
590  socket->read(headerSize); // Discard header from read buffer
591 
592  frame.m_payload = socket->read(frame.m_payloadSize);
593 
594  // Unmask payload
595  if (frame.m_isMasked)
596  {
597  frame.m_mask = header.right(4);
598  for (uint i = 0; i < frame.m_payloadSize; i++)
599  frame.m_payload[i] = frame.m_payload.at(i) ^ frame.m_mask[i % 4];
600  }
601 
605  {
606  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker - Incomplete multi-part frame? Expected continuation.");
607  SendClose(kCloseProtocolError, "Incomplete multi-part frame? Expected continuation.");
608  return;
609  }
610 
611  // Check control frame validity
612  if (frame.m_opCode >= 0x08)
613  {
614  if (!frame.m_finalFrame)
615  {
616  SendClose(kCloseProtocolError, "Control frames MUST NOT be fragmented");
617  return;
618  }
619  if (frame.m_payloadSize > 125)
620  {
621  SendClose(kCloseProtocolError, "Control frames MUST NOT have payload greater than 125 bytes");
622  return;
623  }
624  }
625 
626  switch (frame.m_opCode)
627  {
630  {
631  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker - Received Continuation Frame out of sequence");
632  SendClose(kCloseProtocolError, "Received Continuation Frame out of sequence");
633  return;
634  }
635 
636  m_readFrame.m_payload.append(frame.m_payload);
637 
639  {
640  m_readFrame.m_finalFrame = true;
641  frame = m_readFrame;
642  // Fall through to appropriate handler for complete payload
643  }
644  else
645  break;
646  [[clang::fallthrough]];
649  HandleDataFrame(frame);
650  break;
652  SendPong(frame.m_payload);
653  break;
655  break;
657  if (!frame.m_finalFrame)
658  SendClose(kCloseProtocolError, "Control frames MUST NOT be fragmented");
659  else
661  break;
662  default:
663  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker - Received Unknown Frame Type");
664  break;
665  }
666 
667  frame.reset();
668  }
669 }
670 
672 {
673 }
674 
676 {
677  if (frame.m_finalFrame)
678  {
679  switch (frame.m_opCode)
680  {
682  if (!CodecUtil::isValidUTF8(frame.m_payload))
683  {
684  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker - Message is not valid UTF-8");
685  SendClose(kCloseBadData, "Message is not valid UTF-8");
686  return;
687  }
688  // For Debugging and fuzz testing
689  if (m_fuzzTesting)
690  SendText(frame.m_payload);
691  for (auto *const extension : qAsConst(m_extensions))
692  {
693  if (extension->HandleTextFrame(frame))
694  break;
695  }
696  break;
698  if (m_fuzzTesting)
699  SendBinary(frame.m_payload);
700  for (auto *const extension : qAsConst(m_extensions))
701  {
702  if (extension->HandleBinaryFrame(frame))
703  break;
704  }
705  break;
706  default:
707  break;
708  }
709  m_readFrame.reset();
710  }
711  else
712  {
713  // Start of new fragmented frame
714  m_readFrame.reset();
715  m_readFrame.m_opCode = frame.m_opCode;
718  m_readFrame.m_fragmented = true;
719  m_readFrame.m_finalFrame = false;
720  }
721 }
722 
723 void WebSocketWorker::HandleCloseConnection(const QByteArray &payload)
724 {
725  uint16_t code = kCloseNormal;
726 
727  if (payload.length() == 1)
728  {
729  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker - Invalid close payload");
730  SendClose(kCloseProtocolError, "Invalid close payload");
731  return;
732  }
733 
734  if (payload.length() >= 2)
735  {
736  code = (payload[0] << 8);
737  code |= payload[1] & 0xFF;
738  }
739 
740  if ((code < 1000) ||
741  ((code > 1003) && (code < 1007)) ||
742  ((code > 1011) && (code < 3000)) ||
743  (code > 4999))
744  {
745  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker - Invalid close code received");
746  SendClose(kCloseProtocolError, "Invalid close code");
747  return;
748  }
749 
750  QString closeMessage;
751  if (payload.length() > 2)
752  {
753  QByteArray messageBytes = payload.mid(2);
754  if (!CodecUtil::isValidUTF8(messageBytes))
755  {
756  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker - Message is not valid UTF-8");
757  SendClose(kCloseBadData, "Message is not valid UTF-8");
758  return;
759  }
760  closeMessage = QString(messageBytes);
761  }
762 
763  LOG(VB_HTTP, LOG_INFO, QString("WebSocketWorker - Received CLOSE frame - [%1] %2")
764  .arg(QString::number(code)).arg(closeMessage));
765  SendClose((ErrorCode)code);
766 }
767 
769  const QByteArray &payload)
770 {
771  QByteArray frame;
772 
773  int payloadSize = payload.length();
774 
775  if (payloadSize >= qPow(2,64))
776  {
777  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::CreateFrame() - Payload "
778  "exceeds the allowed size for a single frame");
779  return frame;
780  }
781 
782  if (type >= 0x08)
783  {
784  //if (!m_finalFrame)
785  // SendClose(kCloseProtocolError, "Control frames MUST NOT be fragmented");
786  if (payloadSize > 125)
787  {
788  LOG(VB_GENERAL, LOG_ERR, "WebSocketWorker::CreateFrame() - Control frames MUST NOT have payload greater than 125bytes");
789  return frame;
790  }
791  }
792 
793  uint16_t header = 0;
794  uint16_t extendedHeader = 0; // For payloads > 125 bytes
795  uint64_t superHeader = 0; // For payloads > 65,535 bytes
796 
797  // Only support single frames for now
798  header |= (1 << 15); // FIN (1 bit)
799  // RSV 1 to RSV 3 (1 bit each)
800  header |= (type << 8); // OpCode (4 bits)
801  header |= (0 << 7); // Mask (1 bit) (Off)
802  if (payloadSize < 126)
803  {
804  header |= payloadSize;
805  frame.insert(2, payload.constData(), payloadSize);
806  }
807  else if (payloadSize <= 65535)
808  {
809  header |= 126;
810  extendedHeader = payloadSize;
811  frame.insert(4, payload.constData(), payloadSize);
812  }
813  else
814  {
815  header |= 127;
816  superHeader = payloadSize;
817  frame.insert(10, payload.constData(), payloadSize);
818  }
819 
820  frame[0] = (uint8_t)((header >> 8) & 0xFF);
821  frame[1] = (uint8_t)(header & 0xFF);
822  if (extendedHeader > 0)
823  {
824  frame[2] = (extendedHeader >> 8) & 0xFF;
825  frame[3] = extendedHeader & 0xFF;
826  }
827  else if (superHeader > 0)
828  {
829  frame[2] = (superHeader >> 56) & 0xFF;
830  frame[3] = (superHeader >> 48) & 0xFF;
831  frame[4] = (superHeader >> 40) & 0xFF;
832  frame[5] = (superHeader >> 32) & 0xFF;
833  frame[6] = (superHeader >> 24) & 0xFF;
834  frame[7] = (superHeader >> 16) & 0xFF;
835  frame[8] = (superHeader >> 8) & 0xFF;
836  frame[9] = (superHeader & 0xFF);
837  }
838 
839  LOG(VB_HTTP, LOG_DEBUG, QString("WebSocketWorker::CreateFrame() - Frame size %1").arg(QString::number(frame.length())));
840 
841  return frame;
842 }
843 
844 bool WebSocketWorker::SendFrame(const QByteArray &frame)
845 {
846  if (!m_socket || !m_socket->isOpen() || !m_socket->isWritable())
847  {
849  return false;
850  }
851 
852  LOG(VB_HTTP, LOG_DEBUG, QString("WebSocketWorker::SendFrame() - '%1'...").arg(QString(frame.left(64).toHex())));
853 
854  m_socket->write(frame.constData(), frame.length());
855 
856  return true;
857 }
858 
859 bool WebSocketWorker::SendText(const QString &message)
860 {
861  return SendText(message.trimmed().toUtf8());
862 }
863 
864 bool WebSocketWorker::SendText(const QByteArray& message)
865 {
866  if (!CodecUtil::isValidUTF8(message))
867  {
868  LOG(VB_GENERAL, LOG_ERR, QString("WebSocketWorker::SendText('%1...') - "
869  "Text contains invalid UTF-8 character codes. Discarded.")
870  .arg(QString(message.left(20))));
871  return false;
872  }
873 
874  QByteArray frame = CreateFrame(WebSocketFrame::kOpTextFrame, message);
875 
876  return !frame.isEmpty() && SendFrame(frame);
877 }
878 
879 bool WebSocketWorker::SendBinary(const QByteArray& data)
880 {
881  QByteArray frame = CreateFrame(WebSocketFrame::kOpBinaryFrame, data);
882 
883  return !frame.isEmpty() && SendFrame(frame);
884 }
885 
886 bool WebSocketWorker::SendPing(const QByteArray& payload)
887 {
888  QByteArray frame = CreateFrame(WebSocketFrame::kOpPing, payload);
889 
890  return !frame.isEmpty() && SendFrame(frame);
891 }
892 
893 bool WebSocketWorker::SendPong(const QByteArray& payload)
894 {
895  QByteArray frame = CreateFrame(WebSocketFrame::kOpPong, payload);
896 
897  return !frame.isEmpty() && SendFrame(frame);
898 }
899 
901  const QString &message)
902 {
903  LOG(VB_HTTP, LOG_DEBUG, "WebSocketWorker::SendClose()");
904  QByteArray payload;
905  payload.resize(2);
906  payload[0] = (uint8_t)(errCode >> 8);
907  payload[1] = (uint8_t)(errCode & 0xFF);
908 
909  if (!message.isEmpty())
910  payload.append(message.toUtf8());
911 
912  QByteArray frame = CreateFrame(WebSocketFrame::kOpClose, payload);
913 
914  if (!frame.isEmpty() && SendFrame(frame))
915  {
916  CloseConnection();
917  return true;
918  }
919 
920  CloseConnection();
921  return false;
922 }
923 
925 {
926  SendPing(QByteArray("HeartBeat"));
927 }
928 
930 {
931  if (!extension)
932  return;
933 
934  connect(extension, &WebSocketExtension::SendTextMessage,
935  this, qOverload<const QString &>(&WebSocketWorker::SendText));
936  connect(extension, &WebSocketExtension::SendBinaryMessage,
938 
939  m_extensions.append(extension);
940 }
941 
943 {
944  if (!extension)
945  return;
946 
947  for (auto it = m_extensions.begin(); it != m_extensions.end(); ++it)
948  {
949  if ((*it) == extension)
950  {
951  it = m_extensions.erase(it);
952  break;
953  }
954  }
955 }
956 
957 
WebSocketWorker::m_webSocketServer
WebSocketServer & m_webSocketServer
Definition: websocket.h:274
WebSocketWorkerThread::m_webSocketServer
WebSocketServer & m_webSocketServer
Definition: websocket.h:171
mythevent.h
bool
bool
Definition: pxsup2dast.c:30
kSSLServer
@ kSSLServer
Definition: serverpool.h:33
WebSocketWorker::ProcessFrames
void ProcessFrames(QTcpSocket *socket)
Returns false if an error occurs.
Definition: websocket.cpp:484
hardwareprofile.smolt.timeout
float timeout
Definition: smolt.py:103
WebSocketWorker::m_eventLoop
QEventLoop * m_eventLoop
Definition: websocket.h:273
WebSocketFrame::m_opCode
OpCode m_opCode
Definition: websocket.h:116
ServerPool::setMaxPendingConnections
void setMaxPendingConnections(int n)
Definition: serverpool.h:94
WebSocketWorker::kCloseBadData
@ kCloseBadData
Definition: websocket.h:232
WebSocketWorker::ErrorCode
ErrorCode
Definition: websocket.h:219
WebSocketFrame::m_finalFrame
bool m_finalFrame
Definition: websocket.h:113
WebSocketFrame
A representation of a single WebSocket frame.
Definition: websocket.h:75
CodecUtil::isValidUTF8
static bool isValidUTF8(const QByteArray &data)
Definition: codecutil.cpp:6
WebSocketFrame::m_isMasked
bool m_isMasked
Definition: websocket.h:117
codecutil.h
MThreadPool::maxThreadCount
int maxThreadCount(void) const
Definition: mthreadpool.cpp:510
WebSocketWorker::~WebSocketWorker
~WebSocketWorker() override
Definition: websocket.cpp:129
WebSocketWorkerThread::m_socketFD
qt_socket_fd_t m_socketFD
Definition: websocket.h:172
arg
arg(title).arg(filename).arg(doDelete))
websocket.h
WebSocketWorker::m_sslConfig
QSslConfiguration m_sslConfig
Definition: websocket.h:289
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
WebSocketServer::newTcpConnection
void newTcpConnection(qt_socket_fd_t socket) override
Definition: websocket.cpp:49
WebSocketWorker::m_extensions
QList< WebSocketExtension * > m_extensions
Definition: websocket.h:294
WebSocketWorker::HandleDataFrame
void HandleDataFrame(const WebSocketFrame &frame)
Definition: websocket.cpp:675
MThreadPool::Stop
void Stop(void)
Definition: mthreadpool.cpp:266
MThreadPool::startReserved
void startReserved(QRunnable *runnable, const QString &debugName, std::chrono::milliseconds waitForAvailMS=0ms)
Definition: mthreadpool.cpp:360
WebSocketExtension
Base class for extensions.
Definition: websocket.h:136
WebSocketFrame::kOpTextFrame
@ kOpTextFrame
Definition: websocket.h:104
WebSocketWorker::SendBinary
bool SendBinary(const QByteArray &data)
Definition: websocket.cpp:879
WebSocketWorkerThread::m_connectionType
PoolServerType m_connectionType
Definition: websocket.h:173
WebSocketExtension::SendBinaryMessage
void SendBinaryMessage(const QByteArray &)
WebSocketWorker::CloseConnection
void CloseConnection()
Definition: websocket.cpp:152
WebSocketWorker::m_connectionType
PoolServerType m_connectionType
Definition: websocket.h:277
WebSocketFrame::kOpPong
@ kOpPong
Definition: websocket.h:109
WebSocketWorker::m_heartBeat
QTimer * m_heartBeat
Definition: websocket.h:286
mythlogging.h
WebSocketWorker::SendPing
bool SendPing(const QByteArray &payload)
Definition: websocket.cpp:886
WebSocketWorker::SetupSocket
void SetupSocket()
Definition: websocket.cpp:161
WebSocketFrame::kOpContinuation
@ kOpContinuation
Definition: websocket.h:103
WebSocketWorker::WebSocketWorker
WebSocketWorker(WebSocketServer &webSocketServer, qt_socket_fd_t sock, PoolServerType type, const QSslConfiguration &sslConfig)
Definition: websocket.cpp:102
WebSocketExtension::SendTextMessage
void SendTextMessage(const QString &)
WebSocketWorker::HandleControlFrame
void HandleControlFrame(const WebSocketFrame &frame)
Returns false if an error occurs.
Definition: websocket.cpp:671
WebSocketWorker::SendFrame
bool SendFrame(const QByteArray &frame)
Definition: websocket.cpp:844
WebSocketWorkerThread
The thread in which WebSocketWorker does it's thing.
Definition: websocket.h:157
WebSocketWorker::m_isRunning
bool m_isRunning
Definition: websocket.h:284
WebSocketWorker::SendClose
bool SendClose(ErrorCode errCode, const QString &message=QString())
Definition: websocket.cpp:900
PoolServerType
PoolServerType
Definition: serverpool.h:29
WebSocketFrame::m_payload
QByteArray m_payload
Definition: websocket.h:114
WebSocketWorker::RegisterExtension
void RegisterExtension(WebSocketExtension *extension)
Definition: websocket.cpp:929
WebSocketWorker::m_webSocketMode
bool m_webSocketMode
Definition: websocket.h:280
WebSocketWorker::SendPong
bool SendPong(const QByteArray &payload)
Definition: websocket.cpp:893
WebSocketWorker::kCloseUnexpectedErr
@ kCloseUnexpectedErr
Definition: websocket.h:236
WebSocketWorkerThread::WebSocketWorkerThread
WebSocketWorkerThread(WebSocketServer &webSocketServer, qt_socket_fd_t sock, PoolServerType type, const QSslConfiguration &sslConfig)
Definition: websocket.cpp:70
uint
unsigned int uint
Definition: compat.h:140
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:60
WebSocketServer::m_threadPool
MThreadPool m_threadPool
Definition: websocket.h:58
WebSocketServer::m_rwlock
QReadWriteLock m_rwlock
Definition: websocket.h:57
WebSocketServer::IsRunning
bool IsRunning(void) const
Definition: websocket.h:45
WebSocketWorker::doRead
void doRead()
Definition: websocket.cpp:284
WebSocketFrame::m_payloadSize
uint64_t m_payloadSize
Definition: websocket.h:115
WebSocketFrame::reset
void reset(void)
Definition: websocket.h:89
WebSocketFrame::kOpClose
@ kOpClose
Definition: websocket.h:107
WebSocketServer::m_running
bool m_running
Definition: websocket.h:59
WebSocketWorker
Performs all the protocol-level work for a single websocket connection.
Definition: websocket.h:197
mythcorecontext.h
WebSocketWorker::m_readFrame
WebSocketFrame m_readFrame
Definition: websocket.h:281
WebSocketServer::WebSocketServer
WebSocketServer()
Definition: websocket.cpp:25
WebSocketWorker::kCloseTooLarge
@ kCloseTooLarge
Definition: websocket.h:234
WebSocketServer::~WebSocketServer
~WebSocketServer() override
Definition: websocket.cpp:40
WebSocketWorkerThread::run
void run(void) override
Definition: websocket.cpp:85
WebSocketWorker::SendHeartBeat
void SendHeartBeat()
Definition: websocket.cpp:924
WebSocketFrame::OpCode
OpCode
Definition: websocket.h:101
WebSocketWorker::Exec
void Exec()
Definition: websocket.cpp:147
WebSocketWorker::HandleCloseConnection
void HandleCloseConnection(const QByteArray &payload)
Definition: websocket.cpp:723
uint16_t
unsigned short uint16_t
Definition: iso6937tables.h:3
WebSocketFrame::m_mask
QByteArray m_mask
Definition: websocket.h:118
MThreadPool::setMaxThreadCount
void setMaxThreadCount(int maxThreadCount)
Definition: mthreadpool.cpp:516
WebSocketWorker::m_socket
QTcpSocket * m_socket
Definition: websocket.h:276
WebSocketServer
The WebSocket server, which listens for connections.
Definition: websocket.h:37
WebSocketWorker::DeregisterExtension
void DeregisterExtension(WebSocketExtension *extension)
Definition: websocket.cpp:942
websocket_mythevent.h
WebSocketWorker::CreateFrame
static QByteArray CreateFrame(WebSocketFrame::OpCode type, const QByteArray &payload)
Definition: websocket.cpp:768
WebSocketWorker::m_errorCount
uint8_t m_errorCount
Definition: websocket.h:283
WebSocketWorkerThread::m_sslConfig
QSslConfiguration m_sslConfig
Definition: websocket.h:175
MythCoreContext::CheckSubnet
bool CheckSubnet(const QAbstractSocket *socket)
Check if a socket is connected to an approved peer.
Definition: mythcorecontext.cpp:1276
WebSocketFrame::m_fragmented
bool m_fragmented
Definition: websocket.h:119
WebSocketFrame::kOpPing
@ kOpPing
Definition: websocket.h:108
qt_socket_fd_t
qintptr qt_socket_fd_t
Definition: mythqtcompat.h:4
WebSocketServer::m_sslConfig
QSslConfiguration m_sslConfig
Definition: websocket.h:62
WebSocketFrame::kOpBinaryFrame
@ kOpBinaryFrame
Definition: websocket.h:105
WebSocketWorker::kCloseNormal
@ kCloseNormal
Definition: websocket.h:221
WebSocketWorker::CleanupSocket
void CleanupSocket()
Definition: websocket.cpp:223
WebSocketWorker::kCloseProtocolError
@ kCloseProtocolError
Definition: websocket.h:223
kTCPServer
@ kTCPServer
Definition: serverpool.h:31
WebSocketWorker::m_socketFD
qt_socket_fd_t m_socketFD
Definition: websocket.h:275
WebSocketMythEvent
Extension for sending MythEvents over WebSocketServer.
Definition: websocket_mythevent.h:14
WebSocketWorker::SendText
bool SendText(const QString &message)
Definition: websocket.cpp:859
WebSocketWorker::m_fuzzTesting
bool m_fuzzTesting
Definition: websocket.h:292
WebSocketWorker::ProcessHandshake
bool ProcessHandshake(QTcpSocket *socket)
Definition: websocket.cpp:312