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