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