MythTV master
mythwebsocket.cpp
Go to the documentation of this file.
1// Std
2#include <algorithm>
3#include <chrono>
4using namespace std::chrono_literals;
5#include <limits> // workaround QTBUG-90395
6
7// Qt
8#include <QThread>
9#include <QtEndian>
10#include <QTcpSocket>
11
12// MythTV
13#include "mythlogging.h"
14#include "mythrandom.h"
15#include "http/mythwebsocket.h"
16
17#define LOC QString("WS: ")
18static constexpr int64_t MAX_FRAME_SIZE { 1048576 }; // 1MB
19static constexpr int64_t MAX_MESSAGE_SIZE { 4194304 }; // 4MB
20
32MythWebSocket* MythWebSocket::CreateWebsocket(bool Server, QTcpSocket *Socket,
33 MythSocketProtocol Protocol, bool Testing)
34{
35 if (Socket && (MythSocketProtocol::ProtHTTP != Protocol))
36 return new MythWebSocket(Server, Socket, Protocol, Testing);
37 return nullptr;
38}
39
40MythWebSocket::MythWebSocket(bool Server, QTcpSocket *Socket, MythSocketProtocol Protocol, bool Testing)
41 : m_socket(Socket),
42 m_protocol(Protocol),
43 m_formatForProtocol(MythWS::FrameFormatForProtocol(Protocol)),
44 m_preferRawText(MythWS::UseRawTextForProtocol(Protocol)),
45 m_testing(Testing),
46 m_timer(Testing ? nullptr : new QTimer()),
47 m_serverSide(Server)
48{
49 // Connect read/write signals
50 connect(m_socket, &QTcpSocket::readyRead, this, &MythWebSocket::Read);
51 connect(m_socket, &QTcpSocket::bytesWritten, this, &MythWebSocket::Write);
52
53 LOG(VB_HTTP, LOG_INFO, LOC + QString("Websocket setup: Protocol:%1 (Raw text: %2) Testing:%3")
55 .arg(m_preferRawText).arg(m_testing));
56
57 if (m_timer)
58 {
60 m_timer->start(20s);
61 }
62}
63
65{
66#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
67 delete m_utf8CodecState;
68#else
69 delete m_toUtf16;
70#endif
71 delete m_timer;
72}
73
82{
83 SendClose(WSCloseGoingAway, "Exiting");
84}
85
86void MythWebSocket::SendTextFrame(const QString& Text)
87{
89}
90
91void MythWebSocket::SendBinaryFrame(const QByteArray& Data)
92{
94}
95
97{
98 QString errormsg;
99 while (m_socket->bytesAvailable())
100 {
101 errormsg.clear();
102 // For small frames this should process everything without looping -
103 // including zero length payloads
104 if (ReadHeader == m_readState)
105 {
106 // Header must mean a new frame
107 m_currentFrame = nullptr;
108
109 // we need at least 2 bytes to start reading
110 if (m_socket->bytesAvailable() < 2)
111 return;
112
114 auto code = m_currentFrame->m_opCode;
115 bool control = (code & 0x8) != 0;
116 bool fragmented = (!m_dataFragments.empty()) || (m_string.get() != nullptr);
117 bool final = m_currentFrame->m_final;
118
119 // Invalid use of reserved bits
120 if (m_currentFrame->m_invalid)
121 {
122 errormsg = QStringLiteral("Invalid use of reserved bits");
123 }
124 // Valid opcode
125 else if (!MythWS::OpCodeIsValid(code))
126 {
127 errormsg = QStringLiteral("Invalid use of reserved opcode");
128 }
129 // Clients must be sending masked frames and vice versa
130 else if (m_serverSide != m_currentFrame->m_masked)
131 {
132 errormsg = QStringLiteral("Masking error");
133 }
134 // Invalid control frame size
135 else if (control && (m_currentFrame->m_length > 125))
136 {
137 errormsg = QStringLiteral("Control frame payload too large");
138 // need to acknowledge when an OpClose is received
139 if (WSOpClose == m_currentFrame->m_opCode)
140 m_closeReceived = true;
141 }
142 // Control frames cannot be fragmented
143 else if (control && !final)
144 {
145 errormsg = QStringLiteral("Fragmented control frame");
146 }
147 // Continuation frame must have an opening frame
148 else if (!fragmented && code == WSOpContinuation)
149 {
150 errormsg = QStringLiteral("Fragmentation error (no opening frame)");
151 }
152 // Only continuation frames or control frames are expected once in the middle of a message
153 else if (fragmented && (code != WSOpContinuation) && !control)
154 {
155 errormsg = QStringLiteral("Fragmentation error (bad frame)");
156 }
157 // ensure OpCode matches that expected by SubProtocol
158 else if ((!m_testing && m_protocol != ProtFrame) &&
159 (code == WSOpTextFrame || code == WSOpBinaryFrame) &&
160 code != m_formatForProtocol)
161 {
162 errormsg = QStringLiteral("Received incorrect frame type for subprotocol");
163 }
164
165 if (!errormsg.isEmpty())
166 {
167 LOG(VB_GENERAL, LOG_ERR, LOC + errormsg);
169 // Invalidate the frame so it is ignored, but we must carry
170 // on to ensure we process the close reply
171 m_currentFrame->m_invalid = true;
172 }
173
174 if (126 == m_currentFrame->m_length)
175 {
177 }
178 else if (127 == m_currentFrame->m_length)
179 {
181 }
182 else
183 {
186 }
187
188 // Set the OpCode for the entire message in the case of fragmented messages
189 if (!final && (code == WSOpBinaryFrame || code == WSOpTextFrame))
190 m_messageOpCode = code;
191 }
192
194 {
195 if (m_socket->bytesAvailable() < 2)
196 return;
197 auto length = m_socket->read(2);
198 auto size = qFromBigEndian<quint16>(reinterpret_cast<const void *>(length.data()));
200 m_currentFrame->m_length = size;
202 }
203
205 {
206 if (m_socket->bytesAvailable() < 8)
207 return;
208 auto length = m_socket->read(8);
209 auto size = qFromBigEndian<quint64>(reinterpret_cast<const void *>(length.data()));
211 m_currentFrame->m_length = size;
213
214 // Large frame sizes are not needed with current implementations
215 if (!m_testing && (size > MAX_FRAME_SIZE))
216 {
217 LOG(VB_GENERAL, LOG_ERR, LOC + "Frame size is too large");
219 m_currentFrame->m_invalid = true;
220 }
221 }
222
223 if (ReadMask == m_readState)
224 {
225 if (m_socket->bytesAvailable() < 4)
226 return;
227 m_currentFrame->m_mask = m_socket->read(4);
229 }
230
232 {
233 if (!m_currentFrame.get())
234 {
235 LOG(VB_GENERAL, LOG_ERR, LOC + "No frame");
236 SendClose(WSCloseUnexpectedErr, QStringLiteral("Internal error"));
237 return;
238 }
239
240 auto & payload = m_currentFrame->m_payload;
241 if (!payload)
242 {
243 LOG(VB_GENERAL, LOG_ERR, LOC + "No payload allocated");
244 SendClose(WSCloseUnexpectedErr, QStringLiteral("Internal error"));
245 return;
246 }
247
248 auto available = m_socket->bytesAvailable();
249 auto bytesneeded = payload->size() - payload->m_readPosition;
250 // Note: payload size can be zero
251 if (bytesneeded > 0 && available > 0)
252 {
253 auto read = m_socket->read(payload->data() + m_currentFrame->m_payload->m_readPosition,
254 bytesneeded > available ? available : bytesneeded);
255 if (read < 0)
256 {
257 LOG(VB_GENERAL, LOG_ERR, LOC + "Read error");
258 SendClose(WSCloseUnexpectedErr, QStringLiteral("Read error"));
259 return;
260 }
261 payload->m_readPosition += read;
262 bytesneeded -= read;
263 LOG(VB_HTTP, LOG_DEBUG, LOC + QString("Payload read: %1 (%2 of %3)")
264 .arg(read).arg(payload->m_readPosition).arg(payload->size()));
265 }
266
267 // Have we finished reading the current frame
268 if (bytesneeded < 1)
269 {
270 bool valid = !m_currentFrame->m_invalid;
271
272 // unmask payload - TODO Optimise this (e.g SIMD)
273 if (valid && m_currentFrame->m_masked)
274 for (int i = 0; i < payload->size(); ++i)
275 payload->data()[i] ^= m_currentFrame->m_mask[i % 4];
276
277 // Ensure we have the current *message* opcode
278 auto messageopcode = m_currentFrame->m_opCode;
279 if (WSOpContinuation == messageopcode)
280 messageopcode = m_messageOpCode;
281
282 // Validate UTF-8 text on a frame by frame basis, accumulating
283 // into our string pointer. This optimises UTF-8 handling for
284 // internal purposes by converting only once but is less efficient
285 // for echo testing; as we immediately convert the QString back to UTF-8.
286 // Note: In an ideal world we would avoid reading to an intermediary
287 // buffer before converting. QTextStream notionally supports reading
288 // and converting in a single operation (and appears to support
289 // error detection in the conversion) but it can only be used to
290 // read text; its internal buffering will break direct reads
291 // from the socket (for headers, binary frames etc).
292 if (WSOpTextFrame == messageopcode)
293 {
294 m_messageSize += payload->size();
295
296 // Note: we check invalidChars here and remainingChars
297 // when the full message has been received - as fragments
298 // may not be on code point boundaries; whatever that means :)
299 // Note: This still does not catch 3 'fail fast on UTF-8'
300 // Autobahn tests - though we still pass with 'non-strict'.
301 // I can't see a way to pass strictly without breaking
302 // other tests for valid UTF-8
303 if (m_preferRawText)
304 {
305 m_dataFragments.emplace_back(payload);
306#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
307 (void)m_utf8Codec->toUnicode(payload->data(), payload->size(), m_utf8CodecState);
308#endif
309 }
310 else
311 {
312 if (!m_string)
314#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
315 (*m_string).append(m_utf8Codec->toUnicode(payload->data(), payload->size(), m_utf8CodecState));
316#else
317 (*m_string).append(m_toUtf16->decode(*payload));
318#endif
319 }
320
321 if (
322#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
323 m_utf8CodecState->invalidChars
324#else
325 m_toUtf16->hasError()
326#endif
327 )
328 {
329 LOG(VB_HTTP, LOG_ERR, LOC + "Invalid UTF-8");
330 SendClose(WSCloseBadData, "Invalid UTF-8");
331 m_currentFrame->m_invalid = true;
332 valid = false;
333 }
334 }
335
336 // Add this data payload to the message payload
337 if (WSOpBinaryFrame == messageopcode)
338 {
339 m_dataFragments.emplace_back(payload);
340 m_messageSize += payload->size();
341 }
342
343 // Stop clients from sending large messages.
345 {
346 LOG(VB_HTTP, LOG_ERR, LOC + "Message size is too large");
348 m_currentFrame->m_invalid = true;
349 valid = false;
350 }
351
352 LOG(VB_HTTP, LOG_DEBUG, LOC + QString("%1 frame: Final %2 Length: %3 Masked: %4 Valid: %5")
354 .arg(m_currentFrame->m_final).arg(m_currentFrame->m_length)
355 .arg(m_currentFrame->m_masked).arg(valid));
356
357 // Message complete?
358 if (m_currentFrame->m_final)
359 {
360 // Control frames (no fragmentation - so only one payload)
361 if (valid && WSOpPing == m_currentFrame->m_opCode)
362 {
363 PingReceived(payload);
364 }
365 else if (valid && WSOpPong == m_currentFrame->m_opCode)
366 {
367 PongReceived(payload);
368 }
369 else if (valid && WSOpClose == m_currentFrame->m_opCode)
370 {
371 CloseReceived(payload);
372 }
373 else
374 {
375 // Final UTF-8 validation
376 if ((WSOpTextFrame == messageopcode))
377 {
378 if (
379#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
380 m_utf8CodecState->remainingChars
381#else
382 m_toUtf16->hasError()
383#endif
384 )
385 {
386 LOG(VB_HTTP, LOG_ERR, LOC + "Invalid UTF-8");
387 SendClose(WSCloseBadData, "Invalid UTF-8");
388 valid = false;
389 }
390#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
391 delete m_utf8CodecState;
392 m_utf8CodecState = new QTextCodec::ConverterState;
393#else
394 delete m_toUtf16;
395 m_toUtf16 = new QStringDecoder;
396#endif
397 }
398
399 // Echo back to the Autobahn server
400 if (valid && m_testing)
401 {
402 if (WSOpTextFrame == messageopcode && m_preferRawText)
404 else if (WSOpTextFrame == messageopcode)
406 else if (WSOpBinaryFrame == messageopcode)
408 }
409 else if (valid)
410 {
411 if (WSOpTextFrame == messageopcode&& m_preferRawText)
413 else if (WSOpTextFrame == messageopcode)
415 else if (WSOpBinaryFrame == messageopcode)
417 }
418
419 // Cleanup
420 m_dataFragments.clear();
421 m_string = nullptr;
422 }
423 }
424
425 // Next read must be a new frame with a fresh header
426 m_currentFrame = nullptr;
428 }
429 }
430 }
431}
432
433void MythWebSocket::Write(int64_t Written)
434{
435 auto buffered = m_socket->bytesToWrite();
436 if (Written)
437 {
438 LOG(VB_HTTP, LOG_DEBUG, LOC + QString("Socket sent %1bytes (still to send %2)")
439 .arg(Written).arg(buffered));
440
441 if (VERBOSE_LEVEL_CHECK(VB_HTTP, LOG_INFO) && m_writeQueue.empty() && !buffered)
442 {
443 if (m_writeTotal > 100)
444 {
445 auto seconds = static_cast<double>(m_writeTime.nsecsElapsed()) / 1000000000.0;
446 auto rate = static_cast<uint64_t>(static_cast<double>(m_writeTotal) / seconds);
447 LOG(VB_HTTP, LOG_INFO, LOC + QString("Wrote %1bytes in %2seconds (%3)")
448 .arg(m_writeTotal).arg(seconds, 8, 'f', 6, '0').arg(MythHTTPWS::BitrateToString(rate)));
449 }
450 m_writeTime.invalidate();
451 m_writeTotal = 0;
452 }
453 }
454
455 if (m_writeQueue.empty())
456 return;
457
458 if (VERBOSE_LEVEL_CHECK(VB_HTTP, LOG_INFO) && !m_writeTime.isValid())
459 m_writeTime.start();
460
461 // If the client cannot consume data as fast as we are sending it, we just
462 // buffer more and more data in the Qt socket code. So return and wait for
463 // the next write.
464 if (buffered > (HTTP_CHUNKSIZE << 1))
465 {
466 LOG(VB_HTTP, LOG_DEBUG, LOC + "Draining buffers");
467 return;
468 }
469
470 int64_t available = HTTP_CHUNKSIZE;
471 while ((available > 0) && !m_writeQueue.empty())
472 {
473 auto & payload = m_writeQueue.front();
474 auto written = payload->m_writePosition;
475 auto towrite = static_cast<int64_t>(payload->size()) - written;
476 towrite = std::min(towrite, available);
477 auto wrote = m_socket->write(payload->constData() + written, towrite);
478
479 if (wrote < 0)
480 {
481 // If there is a write error, there is little option other than to
482 // fail the socket
483 LOG(VB_GENERAL, LOG_ERR, LOC + "Write error");
484 m_socket->close();
485 return;
486 }
487
488 payload->m_writePosition += wrote;
489 available -= wrote;
490 m_writeTotal += wrote;
491
492 // Have we finished with this payload
493 if (payload->m_writePosition >= payload->size())
494 m_writeQueue.pop_front();
495 }
496}
497
498 /* After both sending and receiving a Close message, an endpoint
499 considers the WebSocket connection closed and MUST close the
500 underlying TCP connection. The server MUST close the underlying TCP
501 connection immediately; the client SHOULD wait for the server to
502 close the connection but MAY close the connection at any time after
503 sending and receiving a Close message, e.g., if it has not received a
504 TCP Close from the server in a reasonable time period. */
506{
507 auto closing = m_closeReceived || m_closeSent;
508 if (closing && !m_closing)
509 {
510 // Repurpose our ping timer as a timeout for the completion of the closing handshake
511 if (m_timer)
512 {
513 m_timer->stop();
514 m_timer->disconnect();
515 }
516 else
517 {
518 m_timer = new QTimer();
519 }
521 m_timer->start(5s);
522 }
523 m_closing = closing;
524
526 m_socket->close();
527}
528
530{
531 m_closeReceived = true;
532
534
535 // Incoming payload must be empty or be at least 2 bytes in size
536 if ((*Payload).size() == 1)
537 {
539 }
540 else if ((*Payload).size() > 1)
541 {
542 // Extra data *may* be available but it *should* be UTF-8 encoded
543 if ((*Payload).size() > 2)
544 {
545 auto utf8 = (*Payload).mid(2);
546#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
547 QTextCodec::ConverterState state;
548 (void)m_utf8Codec->toUnicode(utf8.constData(), utf8.size(), &state);
549 if (state.invalidChars || state.remainingChars)
551#else
552 (void)m_toUtf16->decode(utf8);
553 if(m_toUtf16->hasError())
555#endif
556 }
557
558 if (WSCloseNormal == close)
559 {
560 auto code = qFromBigEndian<quint16>(reinterpret_cast<void*>(Payload->data()));
561 if ((code < 1000) || ((code > 1003) && (code < 1007)) || ((code > 1011) && (code < 3000)) ||
562 (code > 4999))
563 {
564 LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid close code");
566 }
567 else
568 {
569 close = static_cast<WSErrorCode>(code);
570 }
571 }
572 }
573
575 CheckClose();
576}
577
578void MythWebSocket::SendClose(WSErrorCode Code, const QString& Message)
579{
580 if (m_closeSent)
581 return;
582
583 auto header = MythSharedData::CreatePayload(2);
584 (*header)[0] = static_cast<int8_t>((Code >> 8) & 0xff);
585 (*header)[1] = static_cast<int8_t>(Code & 0xff);
587 m_closeSent = true;
588 CheckClose();
589}
590
592{
593 if (m_closeSent || (m_closeReceived && (Code != WSOpClose)))
594 return;
595
596 // Payload size (if needed)
597 auto addNext = [](int64_t acc, const auto & payload)
598 { return payload.get() ? acc + payload->size() : acc; };
599 int64_t length = std::accumulate(Payloads.cbegin(), Payloads.cend(), 0, addNext);
600
601 // Payload for control frames must not exceed 125bytes
602 if ((Code & 0x8) && (length > 125))
603 {
604 LOG(VB_GENERAL, LOG_ERR, LOC + "Control frame payload is too large");
605 return;
606 }
607
608 // Assume no fragmentation for now
609 auto header = MythSharedData::CreatePayload(2);
610 header->data()[0] = static_cast<int8_t>(Code | 0x80);
611 header->data()[1] = static_cast<int8_t>(0);
612
613 // Add mask if necessary
614 DataPayload mask = nullptr;
615 if (!m_serverSide)
616 {
618 for (int i = 0; i < 4; ++i)
619 {
620 mask->data()[i] = (int8_t)MythRandomInt(INT8_MIN, INT8_MAX);
621 }
622 header->data()[1] |= 0x80;
623 }
624
625 // generate correct size
626 DataPayload size = nullptr;
627 if (length < 126)
628 {
629 header->data()[1] |= static_cast<int8_t>(length);
630 }
631 else if (length <= 0xffff)
632 {
633 header->data()[1] |= 126;
635 *reinterpret_cast<quint16*>(size->data()) = qToBigEndian(static_cast<quint16>(length));
636 }
637 else
638 {
639 header->data()[1] |= 127;
641 *reinterpret_cast<quint64*>(size->data()) = qToBigEndian(static_cast<quint64>(length));
642 }
643
644 // Queue header
645 m_writeQueue.emplace_back(header);
646 // Queue size
647 if (size.get())
648 m_writeQueue.emplace_back(size);
649 // Queue mask
650 if (mask.get())
651 m_writeQueue.emplace_back(mask);
652 for (const auto & payload: Payloads)
653 m_writeQueue.emplace_back(payload);
654
655 LOG(VB_HTTP, LOG_DEBUG, LOC + QString("Queued %1 frame: payload size %2 (queue length: %3)")
656 .arg(MythWS::OpCodeToString(Code)).arg(length).arg(m_writeQueue.size()));
657
658 // Kick off the write
659 Write();
660}
661
663{
665 return;
666 SendFrame(WSOpPong, { std::move(Payload) });
667}
668
670{
671 // Validate against the last known ping payload and warn on error
672 auto sizein = Payload.get() ? Payload.get()->size() : 0;
673 auto sizeout = m_lastPingPayload.size();
674 if ((sizein == sizeout) && sizeout > 0)
675 if (*Payload.get() == m_lastPingPayload)
676 return;
677 LOG(VB_HTTP, LOG_DEBUG, LOC + "Unexpected pong payload (this may not be an error)");
678 LOG(VB_HTTP, LOG_DEBUG, LOC + QString("Last Payload is (%1), Pong Payload is (%2)")
679 .arg(m_lastPingPayload.constData(), Payload.get()->constData()));
680}
681
683{
684 m_lastPingPayload = QString::number(MythRandom() & 0xFFFF, 16).toUtf8();
685 if (auto payload = MythSharedData::CreatePayload(m_lastPingPayload); payload.get())
686 SendFrame(WSOpPing, { payload });
687}
688
static QString BitrateToString(uint64_t Rate)
static QString ProtocolToString(MythSocketProtocol Protocol)
static DataPayload CreatePayload(size_t Size)
static StringPayload CreateString()
static QString OpCodeToString(WSOpCode Code)
static bool OpCodeIsValid(WSOpCode Code)
static WSFrame CreateFrame(const QByteArray &Header)
An implementation of the WebSocket protocol...
Definition: mythwebsocket.h:21
QTimer * m_timer
Definition: mythwebsocket.h:67
WSQueue m_writeQueue
Definition: mythwebsocket.h:87
void Close()
Initiate the close handshake when we are exiting.
DataPayloads m_dataFragments
Definition: mythwebsocket.h:77
void PingReceived(DataPayload Payload)
void SendTextFrame(const QString &Text)
void Write(int64_t Written=0)
static MythWebSocket * CreateWebsocket(bool Server, QTcpSocket *Socket, MythSocketProtocol Protocol, bool Testing)
StringPayload m_string
Definition: mythwebsocket.h:78
void NewRawTextMessage(const DataPayloads &Payloads)
void NewBinaryMessage(const DataPayloads &Payloads)
WSFrame m_currentFrame
Definition: mythwebsocket.h:74
WSOpCode m_messageOpCode
Definition: mythwebsocket.h:75
void SendFrame(WSOpCode Code, const DataPayloads &Payloads)
bool m_preferRawText
Definition: mythwebsocket.h:65
void CloseReceived(const DataPayload &Payload)
QElapsedTimer m_writeTime
Definition: mythwebsocket.h:89
QTcpSocket * m_socket
Definition: mythwebsocket.h:62
void PongReceived(const DataPayload &Payload)
void SendBinaryFrame(const QByteArray &Data)
int64_t m_writeTotal
Definition: mythwebsocket.h:88
void NewTextMessage(const StringPayload &Text)
void SendClose(WSErrorCode Code, const QString &Message={})
MythWebSocket(bool Server, QTcpSocket *Socket, MythSocketProtocol Protocol, bool Testing)
MythSocketProtocol m_protocol
Definition: mythwebsocket.h:63
int64_t m_messageSize
Definition: mythwebsocket.h:76
QStringDecoder * m_toUtf16
Definition: mythwebsocket.h:83
WSOpCode m_formatForProtocol
Definition: mythwebsocket.h:64
ReadState m_readState
Definition: mythwebsocket.h:68
bool m_closeReceived
Definition: mythwebsocket.h:71
~MythWebSocket() override
QByteArray m_lastPingPayload
Definition: mythwebsocket.h:86
#define close
Definition: compat.h:39
#define HTTP_CHUNKSIZE
A group of functions shared between HTTP and WebSocket code.
std::vector< DataPayload > DataPayloads
std::shared_ptr< MythSharedData > DataPayload
MythSocketProtocol
@ ProtHTTP
@ ProtFrame
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
Convenience inline random number generator functions.
#define LOC
static constexpr int64_t MAX_MESSAGE_SIZE
static constexpr int64_t MAX_FRAME_SIZE
@ WSOpTextFrame
@ WSOpBinaryFrame
@ WSOpContinuation
@ WSOpClose
@ WSOpPing
@ WSOpPong
WSErrorCode
@ WSCloseTooLarge
@ WSCloseBadData
@ WSCloseGoingAway
@ WSCloseUnexpectedErr
@ WSCloseNormal
@ WSCloseProtocolError
int MythRandomInt(int min, int max)
generate a uniformly distributed random signed int in the closed interval [min, max].
Definition: mythrandom.h:58
uint32_t MythRandom()
generate 32 random bits
Definition: mythrandom.h:20
def read(device=None, features=[])
Definition: disc.py:35