-
Notifications
You must be signed in to change notification settings - Fork 5
/
WebSocket.h
598 lines (520 loc) · 34.1 KB
/
WebSocket.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
//
// WebSocket - A lightweight RFC 6455 (Web Socket) implementation for Qt5
// Copyright (C) 2019-2020 Calin A. Culianu <[email protected]>
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.#pragma once
//
#include <QByteArray>
#include <QHash>
#include <QList>
#include <QMetaObject>
#include <QObject>
#include <QPointer>
#include <QTcpSocket>
#include <cstddef>
#include <exception>
#include <list>
#include <optional>
#include <stdexcept>
#ifdef __clang__
#pragma clang diagnostic push
// turn off the dreaded "warning: class padded with xx bytes, etc" since we aren't writing wire protocols using structs..
#pragma clang diagnostic ignored "-Wpadded"
#endif
class QTcpSocket;
class QTimer;
/// An implementation of RFC 6455 (Web Sockets) for Qt5. Most client code will want to skip down below and just
/// use the WebSocket::Wrapper class, which wraps a QTcpSocket.
namespace WebSocket
{
/// All exceptions thrown by the code below are derived from this base type.
struct Exception : public std::runtime_error { Exception(const QString &s); ~Exception(); };
/// May be thrown by the various Ser and Deser functions if the arguments supplied are bad.
struct BadArgs : public Exception { using Exception::Exception; ~BadArgs(); };
/// May be thrown in rare cases if there are assertion failure in the code (shouldn't normally happen).
struct InternalError : public Exception { using Exception::Exception; ~InternalError(); };
/// All higher level errors are a type of this exception (but some functions may throw BadArgs or InternalError
/// as well)
struct Error : public Exception { using Exception::Exception; ~Error(); };
/// Thrown if the parsed or generated data exceeds INT_MAX
struct MessageTooBigError : public Error { using Error::Error; ~MessageTooBigError(); };
/// Default to 4096-byte fragments.
inline constexpr std::size_t DefaultFragmentSize = 4096;
enum FrameType : std::uint8_t {
// Payload (application data) frame types
Text = 0x1, // supports fragmentation
Binary = 0x2, // supports fragmentation
// Control frame types
Ctl_Close = 0x8, // may contain up to 125 bytes of optional payload data for the reasonCode (2 bytes) + message
Ctl_Ping = 0x9, // may contain up to 125 bytes of optional payload data
Ctl_Pong = 0xA, // may contain up to 125 bytes of payload data (must be same data echoed back from the Ping data, if any)
// Internal use only
_Continuation = 0x0, // "continuation" of Text/Binary (all fragments after the first use this opcode)
};
QString frameTypeName(FrameType);
/// The below codes are all from RFC 6455
enum CloseCode : std::uint16_t {
/// 1000 indicates a normal closure, meaning that the purpose for which the connection was established has been fulfilled.
Normal = 1000,
/// 1001 indicates that an endpoint is "going away", such as a server going down or a browser having navigated away from a page.
GoingAway = 1001,
/// 1002 indicates that an endpoint is terminating the connection due to a protocol error.
ProtocolError = 1002,
/// 1003 indicates that an endpoint is terminating the connection because it has received a type of data it
/// cannot accept (e.g., an endpoint that understands only text data MAY send this if it receives a binary
/// message).
CannotAcceptDataType = 1003,
Reserved1 = 1004,
Reserved2 = 1005,
Reserved3 = 1006,
/// 1007 indicates that an endpoint is terminating the connection because it has received data within a message
/// that was not consistent with the type of the message (e.g., non-UTF-8 [RFC3629] data within a text message).
BadData = 1007,
/// 1008 indicates that an endpoint is terminating the connection because it has received a message that violates
/// its policy. This is a generic status code that can be returned when there is no other more suitable status
/// code (e.g., 1003 or 1009) or if there is a need to hide specific details about the policy.
PolicyViolated = 1008,
/// 1009 indicates that an endpoint is terminating the connection because it has received a message that is too
/// big for it to process.
MessageTooBig = 1009,
/// 1010 indicates that an endpoint (client) is terminating the connection because it has expected the server
/// to negotiate one or more extension, but the server didn't return them in the response message of the
/// WebSocket handshake. The list of extensions that are needed SHOULD appear in the /reason/ part of the
/// Close frame. Note that this status code is not used by the server, because it can fail the WebSocket
/// handshake instead.
ExtensionMissing = 1010,
/// 1011 indicates that a server is terminating the connection because it encountered an unexpected condition
/// that prevented it from fulfilling the request.
ServerCannotFulfillRequest = 1011,
Reserved4 = 1015,
/* From the RFC:
7.4.2. Reserved Status Code Ranges
0-999
Status codes in the range 0-999 are not used.
1000-2999
Status codes in the range 1000-2999 are reserved for definition by
this protocol, its future revisions, and extensions specified in a
permanent and readily available public specification.
3000-3999
Status codes in the range 3000-3999 are reserved for use by
libraries, frameworks, and applications. These status codes are
registered directly with IANA. The interpretation of these codes
is undefined by this protocol.
4000-4999
Status codes in the range 4000-4999 are reserved for private use
and thus can't be registered. Such codes can be used by prior
agreements between WebSocket applications. The interpretation of
these codes is undefined by this protocol.
*/
};
/// Functions for generating wire protocol data frames
namespace Ser {
/// Given a complete payload of data, inserts the proper framing into the data stream, and returns the same data
/// with the framing inserted (and properly fragmented). fragmentSize controls the size of fragments.
///
/// Note that only FrameType::Text and FrameType::Binary frames may be fragmented. The Ctl_* frames may not (their
/// payload may only consist of a single fragment of up to 125 bytes in length).
///
/// If isMasked == true, then the masking key is selected by calling QRandomGenerator::global()->generate(). The
/// masking key is XOR'd with the data payload and is also embedded into the frame header. The WebSockets RFC says
/// that all client-originating messages must be masked, including control frames.
///
/// The fragmentSize argument is ignored for Ctl_* frame types.
///
/// May throw BadArgs if:
/// - fragmentSize is 0 and `type` is Text or Binary
/// - fragmentSize is > a 63-bit integer and `type` is Text or Binary
/// - data.size() > 125 and `type` is one of the Ctl_* types.
///
/// May also throw MessageTooBigError if:
/// - the resuling data would exceed the maximum size of a QByteArray (currently INT_MAX)
QByteArray wrapPayload(const QByteArray &data, FrameType type, bool isMasked, std::size_t fragmentSize = DefaultFragmentSize);
/// Convenience function that wraps 'data' using the 'Text' data frame opcode. Note that 'data' must be Utf8 encoded
/// text or else the other side may terminate the connection.
inline QByteArray wrapText(const QByteArray &data, bool isMasked, std::size_t fragmentSize = DefaultFragmentSize) {
return wrapPayload(data, FrameType::Text, isMasked, fragmentSize);
}
/// Convenience function that wraps 'data' using the 'Data' data frame opcode.
inline QByteArray wrapBinary(const QByteArray &data, bool isMasked, std::size_t fragmentSize = DefaultFragmentSize) {
return wrapPayload(data, FrameType::Binary, isMasked, fragmentSize);
}
/// Convenience function that creates a PING control frame. Optionally up to 125 bytes worth of data may be
/// sent to the other end, which will be echoed back in its PONG reply.
inline QByteArray makePingFrame(bool isMasked, const QByteArray & data = {}) {
return wrapPayload(data.size() > 125 ? data.left(125) : data, FrameType::Ctl_Ping, isMasked);
}
/// Convenience function that creates a PONG reply control frame. Note that the reply must contain the same data
/// received from the corresponding PING that this frame is in reply to.
inline QByteArray makePongFrame(const QByteArray & data, bool isMasked) {
return wrapPayload(data.size() > 125 ? data.left(125) : data, FrameType::Ctl_Pong, isMasked);
}
/// Returns a "Close" control frame with empty data payload (which is prefectly ok to send as per the RFC)
inline QByteArray makeCloseFrame(bool isMasked) {
return wrapPayload({}, FrameType::Ctl_Close, isMasked);
}
/// Returns a "Close" control frame with non-empty data payload. The payload will consist of 2-byte code + reason (reason may be empty)
QByteArray makeCloseFrame(bool isMasked, CloseCode code, const QByteArray &reason = {});
} // end namespace Ser
namespace Deser {
struct Frame {
FrameType type = FrameType::Text;
bool masked{}; ///< true iff the data was masked as it came in from the wire. Servers must enforce that clients send masked data.
QByteArray payload{}; ///< the actual "payload data" from the payload. Note in the case of Text/Binary this is always fully assembled from all fragments.
/* control frames always have high bit in low order nibble set. */
inline constexpr bool isControl() const noexcept { return type & 0x08; }
};
/// Thrown if the incoming wire data is out-of-spec and/or invalid.
struct ProtocolError : public Error { using Error::Error; ~ProtocolError(); };
enum MaskEnforcement {
DontCare = 0,
RequireMasked = 1,
RequireUnmasked = 2,
};
/// Modifies `buf` in-place, extracting out the control frames it encounters as well as fully assembled Text/Binary
/// messages (after all fragments have been found). Leaves any data in `buf` that it could not process untouched (e.g.
/// incomplete data due to missing ending FIN frames). This function is designed to work with asynch TCP socket
/// code as a parsing/deserialization function of an ongoing read buffer.
///
/// Does full validation of input and throws on validation error. May throw ProtocolError or MessageTooBigError.
/// If MaskEnforcement is enabled, then it will also throw ProtocolError if the mask predicate is violated for
/// any frames encountered.
///
/// Note: Potentially InternalError can be thrown if there are bugs in this code -- calling code may wish
/// to catch that exception as well and abort the app in that case.
std::list<Frame> parseBuffer(QByteArray &buf, MaskEnforcement maskEnforcement = DontCare);
/// Convenience helper for parsing out the CloseCode and the reason from a Close frame.
struct CloseFrameInfo {
std::optional<quint16> code; // see enum CloseCode for possible codes specified in RFC. This may !has_value() if no code was specified.
QByteArray reason; // remaining bytes after parsing out code from paylaod, if any
CloseFrameInfo() = default;
/// Construct this from a Frame, parsing out the code (if any) and the remaining bytes put into 'reason'
inline CloseFrameInfo(const Frame &f) { *this = f; }
CloseFrameInfo &operator=(const Frame &);
};
}
/// Handshake manager objects for automatically performing a websocket handshake and transitioning the other endpoint
/// into WebSocket mode.
namespace Handshake {
/// Asynchronous managers
namespace Async {
/// Base class -- common code used by both ClientSide and ServerSide below.
class ClientServerBase : public QObject {
Q_OBJECT
public:
explicit ClientServerBase(QTcpSocket * /* may not be nullptr */);
~ClientServerBase() override;
static constexpr int kDefaultTimeout = 10000, ///< 10s handshake timeout default
kDefaultMaxHeaders = 8192;
/// Get the underlying socket.
inline QTcpSocket *socket() const { return const_cast<QTcpSocket *>(sock); }
/// We will fail if the incoming headers exceed this size
inline int maxHeaderBytes() const { return maxHeaders; }
/// Set failure threshold. Set to <= 0 to allow unlimited headers.
inline void setMaxHeaderBytes(int max) { maxHeaders = max; }
/// If true, we will call this->deleteLater() on ourselves after finished() is emitted. Default true.
inline bool autoDelete() const { return autodelete; }
inline void setAutoDelete(bool b) { autodelete = b; }
/// If true, we will call socket->disconnectFromHost() after failure() is emitted.
inline bool audoDisconnect() const { return autodisconnect; }
inline void setAutoDisconnect(bool b) { autodisconnect = b; }
signals:
// One of the below will be emitted if the socket isn't deleted before completion.
/// Emitted when the handshake has completed successfully. After this is emitted the other endpoint
/// is expecting the WebSocket protocol.
void success();
/// Emitted if the handshake timed out or some other error occurred.
/// Note that if this is emitted, and autoDisconnect() is true, the socket will also be disconnected
/// using disconnectFromHost().
void failure(const QString &reason);
/// Emitted immediately after success() or failure()
void finished();
protected:
using ConnList = QList<QMetaObject::Connection>;
ConnList conns;
QTcpSocket *sock;
QTimer *timer = nullptr;
int maxHeaders = kDefaultMaxHeaders;
bool autodelete = true, autodisconnect = true;
int nread = 0;
QHash<QString, QString> headers;
bool checkHeaders(QString *what = nullptr) const;
/// called from derived classes' start(), returns false if should abort start(), emits failure(reason)
/// on false return. On true return, sets up some private signal/slot connections for emitting failure
/// on various socket errors and timeout.
bool startCommon(int timeout);
virtual void on_ReadyRead() = 0;
};
/// Negotiates a handshake for a given QTcpSocket, from the client side.
///
/// Use this with a freshly connected socket before transmitting any data to get the other end to speak
/// the WebSocket protocol. WSS is supported: the socket may be a QSslSocket in which case it should be
/// used with this class after the QSslSocket has emitted the `encrypted()` signal (that is, right after the
/// TLS handshake has completed).
class ClientSide : public ClientServerBase {
public:
using ClientServerBase::ClientServerBase; /// re-use c'tor
~ClientSide() override;
/// This begins the asynchronous handshake process. Either success() or failure() will be emitted, with
/// finished() emitted right after in either case.
///
/// This function may be used with either a QTcpSocket or a QSslSocket. For QSslSockets, call it right
/// after the 'encrypted()' signal is emitted and for regular sockets, call this after the 'connected()'
/// signal is emitted. In other words, this should be called precisely once right after the connection
/// is made and before any data is sent to the socket.
///
/// If the handshake is successful the following properties will be written to the QTcpSocket QObject:
///
/// - "websocket-protocol" -> bool, true
/// - "websocket-headers" -> The headers received from the server during the handshake. This is a
/// QVariantMap of header/value pairs, as QStrings without the ':' separator.
/// All keys are lowercased, and values are copied verbatim (after Utf8
/// decode).
///
void start(const QString & resourceName /* e.g. "/" */,
const QString &host /* e.g. "remoteserver.com" */,
const QString &origin = {} /* Akin to http referer. This is optional, Typically omitted for non-browser clients */,
int timeout = kDefaultTimeout /* milliseconds */);
protected:
void on_ReadyRead() override;
private:
QString expectedDigest;
};
/// Negotiates a handshake for a given QTcpSocket, from the server side.
///
/// Use this with a freshly connected socket before transmitting any data to negotiate with the other end
/// to speak the WebSocket protocol. WSS is supported: the socket may be a QSslSocket in which case it
/// should be used with this class after the QSslSocket has emitted the `encrypted()` signal (that is, right
/// after the TLS handshake has completed).
class ServerSide : public ClientServerBase {
public:
using ClientServerBase::ClientServerBase; /// re-use c'tor
~ServerSide() override;
static constexpr auto kDefaultServerAgent = "WebSocket_Qt";
/// This begins the asynchronous handshake process. Either success() or failure() will be emitted, with
/// finished() emitted right after in either case.
///
/// This function may be used with either a QTcpSocket or a QSslSocket. For QSslSockets, call it right
/// after the 'encrypted()' signal is emitted (after you call startServerEncryption()), and for regular
/// sockets call this after the new socket is returned from `QTcpServer::nextPendingConnection()`.
///
/// In other words, this should be called precisely once right after the connection is established fully
/// and before any data is sent to the socket.
///
/// If the handshake is successful the following properties will be written to the QTcpSocket QObject:
///
/// - "websocket-protocol" -> bool, true
/// - "websocket-request-resource" -> QString. The thing they sent us after the HTTP GET, e.g. "/" or "/chat", etc
/// - "websocket-headers" -> The headers received from the client during the handshake. This is a
/// QVariantMap of header/value pairs, as QStrings without the ':' separator.
/// All keys are lowercased, and values are copied verbatim (after Utf8
/// decode).
///
void start(const QString & serverAgent = kDefaultServerAgent /* If empty, the Server: of the response header will be omitted. */,
int timeout = kDefaultTimeout /* milliseconds */);
protected:
void on_ReadyRead() override;
private:
QString serverAgent, reqResource;
};
} // end namespace Async
} // end namespace Handshake
/// QTcpSocket work-alike. Wraps a real underying socket (which it becomes the parent of). Requires an event
/// loop for correct operation (since it uses some asynchronous magic behind the scenes).
///
/// This class is intented to be used asynchronously. The readyRead() or messagesReady() signal can be used for
/// notification when data messages become available. Use readNextMessage() or readAllMessages() to retrieve the
/// buffered messages. Use setReadBufferSize() to limit the amount of buffering done for these messages.
///
/// Writing to the WebSocket can be accomplished via the write() method (as with a regular QTcpSocket), in which
/// case the entire QByteArray buffer is sent down the wire as a single message (possibly fragmented) of
/// either Text or Binary type (default Text). Use setMessageMode() to set the default mode for write(), or
/// use the custom methods sendText() and sendBinary() to explicitly send specific message types on a case-by-case
/// basis.
///
/// PING/PONG support is provided via an optional automatic mechanism (on by default). There is also an automatic
/// PING keepalive sent to the other endpoint (20 second interval, ~60 second timeout by default, configurable).
///
/// CLOSE is handled automatically as well. disconnectFromHost() will use the WebSocket close handshake mechanism
/// with a 3 second timeout (before force-closing the socket). If the other endpoint sends us a CLOSE frame, the
/// handshake is handled asynchronously by this class and eventually disconnected() will be emitted, with the
/// underlying connection being closed.
///
/// This class must be used with a freshly-connected QTcpSocket or QSslSocket (in the QSslSocket case, use this
/// class after the socket has entered encrypted() mode). In both cases no application data should be sent down
/// the underlying socket before the websocket connection handshake is done.
///
/// To start the handshake, use startClientHandshake() or startServerHandshake() (depending on whether you are the
/// client or server endpoint).
class Wrapper : public QTcpSocket
{
Q_OBJECT
public:
enum Mode : std::uint8_t {
Unknown = 0,
ClientMode,
ServerMode,
};
enum MessageMode : std::uint8_t {
Text = FrameType::Text,
Binary = FrameType::Binary
};
using Message = Deser::Frame;
using MessageList = std::list<Message>;
/// This instance will become the parent of socket. The socket should already be in the Connected state.
/// Socket may not be nullptr. If socket is a QSslSocket, it should already be in the 'encrypted' state.
/// Note: Passing a Wrapper * instance as the first argument to this constructor is not supported and the
/// WebSocket handshake will fail.
explicit Wrapper(QTcpSocket *socket, QObject *parent = nullptr);
~Wrapper() override;
inline QTcpSocket *wrappedSocket() const { return socket; }
/// Use this after construction to start the WebSocket handshake as a client. `handshakeSuccess` or
/// `handshakeFailed` will be emitted when the handshake finishes (iff true is returned).
/// No-op if called twice or if socket is not in the connected state, in which case false is returned.
bool startClientHandshake(const QString & resourceName, const QString & host, const QString & origin = {},
int timeout = Handshake::Async::ClientSide::kDefaultTimeout);
/// Use this after construction to start the WebSocket handshake as a server. `handshakeSuccess` or
/// `handshakeFailed` will be emitted when the handshake finishes (iff true is returned).
/// No-op if called twice or if socket is not in the connected state, in which case false is returned.
bool startServerHandshake(const QString & serverAgent = Handshake::Async::ServerSide::kDefaultServerAgent,
int timeout = Handshake::Async::ServerSide::kDefaultTimeout);
/// Returns true iff the socket is now in a valid websocket state and is still connected (after handshake this is true).
bool isValid() const;
/// Read-only property. This is set depending on whether startClientHandshake or startServerHandshake was previously called
Mode mode() const { return _mode; }
/// The default message mode to use when generating frames for the QIODevice::write() function
MessageMode messageMode() const { return _messageMode; }
void setMessageMode(MessageMode m) { _messageMode = m; }
/// If true, we will auto-reply to pings asynchronously in this object's thread's event loop
bool autoPingReply() const { return autopingreply; }
void setAutoPingReply(bool b) { autopingreply = b; }
/// Returns the auto-ping interval in msec. Auto-ping is enabled by default and is set to 20000 (20 secs).
/// When enabled, the other endpoint is automatically pinged at the specified interval. Returns 0 if disabled.
int autoPingInterval() const { return autopinginterval; }
/// Set to <= 0 to disable auto-ping.
void setAutoPingInterval(int msec);
/// The maximum number of messages that may be queued. If more than this number of messages are in the message queue,
/// then disconnectFromHost(PolicyViolated) will be sent to the other endpoint, as a DoS defense. Default: 20000.
unsigned maxMessageQueue() const { return maxframes; }
void setMaxMessageQueue(unsigned val) { if (val) maxframes = val; }
/// From QTcpSocket, etc
void resume() override { socket->resume(); }
bool setSocketDescriptor(qintptr socketDescriptor, SocketState state = ConnectedState, OpenMode openMode = ReadWrite) override
{ return socket->setSocketDescriptor(socketDescriptor, state, openMode); }
qintptr socketDescriptor() const override { return socket->socketDescriptor(); }
using QAbstractSocket::connectToHost;
/// calls underlying socket
void connectToHost(const QString &hostName, quint16 port, OpenMode openMode = ReadWrite, NetworkLayerProtocol protocol = AnyIPProtocol) override
{ socket->connectToHost(hostName, port, openMode, protocol); }
void setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value) override
{ socket->setSocketOption(option, value); }
QVariant socketOption(QAbstractSocket::SocketOption option) override
{ return socket->socketOption(option); }
/// Sends CLOSE frame (Code: Normal 1000), waits 3 seconds for CLOSE reply before timing out the close handshake
/// and aborting the connection unconditionally (this all happens asynch. -- this function returns immediately).
void disconnectFromHost() override;
/// Disconnect from remote host with a specified code and reason. Same semantics as disconnectFromHost().
void disconnectFromHost(CloseCode code, const QByteArray & reason = {});
// From QIODevice
qint64 bytesAvailable() const override { return dataFrameByteCount + readDataPartialBuf.size(); }
qint64 bytesToWrite() const override { return socket->bytesToWrite(); }
bool canReadLine() const override; ///< do not use this
void close() override { if (socket && socket->isOpen()) { emit aboutToClose(); } abort(); setOpenMode(NotOpen); }
bool atEnd() const override { return socket->atEnd(); }
bool flush() { return socket->flush(); } // ### Qt6: remove me (implementation moved to private flush())
void abort() { socket->abort(); }
// From QAbstractSocket:
void setReadBufferSize(qint64 size) override { QTcpSocket::setReadBufferSize(size); socket->setReadBufferSize(size); }
/// Returns the number of Binary and/or Text message frames in the receive message queue.
int messagesAvailable() const { return int(dataMessages.size()); }
/// Pops and returns the next (complete) Binary or Text message waiting in the receive queue, or an empty
/// QByteArray if the receive queue was empty. Note that it's possible for the other endpoint to send empty
/// frames (payload size 0), so to distinguish between that situation and an empty queue, one would need to call
/// messagesAvailable() before calling this function. If the `binary` pointer is specified, will write true or
/// false to it depending on whether the retrieved frame was of Binary or Text type.
QByteArray readNextMessage(bool *binary = nullptr);
/// Pops all of the queued messages off the receive queue and returns them. (All of the messages returned
/// are complete -- no partial messages are ever returned here).
MessageList readAllMessages();
/// TODO: what do we do about all these?!
bool waitForConnected(int msecs = 30000) override { return socket->waitForConnected(msecs); }
//bool waitForHandshake(int msecs = 30000);
bool waitForReadyRead(int msecs = 30000) override { return socket->waitForReadyRead(msecs); }
bool waitForBytesWritten(int msecs = 30000) override { return socket->waitForBytesWritten(msecs); }
bool waitForDisconnected(int msecs = 30000) override { return socket->waitForDisconnected(msecs); }
signals:
/// Emitted after startClientHandshake() or startServerHandshake() is called after successful websocket
/// handshake completion. After this signal is emitted, the socket is in websocket mode and data may be
/// sent/received.
void handshakeSuccess();
/// Emitted after startClientHandshake() or startServerHandshake() is called after a failed websocket handshake.
void handshakeFailed(const QString &errorMessage);
/// Guaranteed to eventually be emitted after startClientHandshake() or startServerHandshake() is called (if
/// they returned true), indicating the handshake process has finished. This is always emitted after
/// handshakeSuccess() and/or handshakeFailed().
void handshakeFinished();
/// Similar in spirit to QSslSocket's "encryptedBytesWritten". Emitted when the underlying socket sends
/// data down the wire. The value here will always be larger than the `bytesWritten` signal this class
/// also emits, since it will account for Web Socket framing + payload data. (In contrast `bytesWritten` reports
/// payload sizes, not counting framing overhead).
void rawBytesWritten(qint64 totalBytes);
void closeFrameReceived(quint16 code, const QByteArray &data);
void pingFrameReceived(const QByteArray &data);
void pongFrameReceived(const QByteArray &data);
/// This is emitted when Text or Binary messages have arrived and are ready for processing.
/// Use readNextMessage() or readAllMessages() to read the completed data frames.
void messagesReady();
public slots:
qint64 sendPing(const QByteArray &data = {});
qint64 sendPong(const QByteArray &data = {});
qint64 sendText(const QByteArray &data);
qint64 sendBinary(const QByteArray &data);
protected:
qint64 readData(char *data, qint64 maxlen) override; ///< this breaks the framing if called.
qint64 readLineData(char *data, qint64 maxlen) override; ///< this breaks the framing if called.
qint64 writeData(const char *data, qint64 len) override; ///< wraps the data in a frame and writes it to socket. uses the frameType specified in messageMode
qint64 sendClose();
qint64 sendClose(quint16 code, const QByteArray &reason = {});
private:
QPointer<QTcpSocket> socket;
qint64 lastPongRecvd = 0;
MessageList dataMessages;
QByteArray readDataPartialBuf; ///< leftover data if readData() was called instead of the readNextMessage() API
QByteArray buf; ///< working buffer for deserializing frames.
qint64 dataFrameByteCount = 0;
unsigned maxframes = 20'000;
Mode _mode = Unknown;
MessageMode _messageMode = Text;
bool disconnectFlag = false;
bool autopingreply = true;
bool sentclose = false, gotclose = false;
int autopinginterval = 20'000;
void on_readyRead();
inline bool isMasked() const { return _mode == ClientMode; }
QTimer *getPingTimer();
static constexpr auto kPingTimer = "_WS_Auto_Ping_";
void miscCleanup();
void startAutoPing();
};
} // end namespace WebSocket
#ifdef __clang__
#pragma clang diagnostic pop
#endif