Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qhttp2connection_p.h
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#ifndef HTTP2CONNECTION_P_H
6#define HTTP2CONNECTION_P_H
7
8//
9// W A R N I N G
10// -------------
11//
12// This file is not part of the Qt API. It exists for the convenience
13// of the Network Access API. This header file may change from
14// version to version without notice, or even be removed.
15//
16// We mean it.
17//
18
19#include <private/qtnetworkglobal_p.h>
20
21#include <QtCore/qobject.h>
22#include <QtCore/qhash.h>
23#include <QtCore/qset.h>
24#include <QtCore/qvarlengtharray.h>
25#include <QtCore/qxpfunctional.h>
26#include <QtNetwork/qhttp2configuration.h>
27#include <QtNetwork/qtcpsocket.h>
28
29#include <private/http2protocol_p.h>
30#include <private/http2streams_p.h>
31#include <private/http2frames_p.h>
32#include <private/hpack_p.h>
33
34#include <variant>
35#include <optional>
36#include <type_traits>
37#include <limits>
38
39class tst_QHttp2Connection;
40
41QT_BEGIN_NAMESPACE
42
43template <typename T, typename Err>
44class QH2Expected
45{
46 static_assert(!std::is_same_v<T, Err>, "T and Err must be different types");
47public:
48 // Rule Of Zero applies
49 QH2Expected(T &&value) : m_data(std::move(value)) { }
50 QH2Expected(const T &value) : m_data(value) { }
51 QH2Expected(Err &&error) : m_data(std::move(error)) { }
52 QH2Expected(const Err &error) : m_data(error) { }
53
54 QH2Expected &operator=(T &&value)
55 {
56 m_data = std::move(value);
57 return *this;
58 }
59 QH2Expected &operator=(const T &value)
60 {
61 m_data = value;
62 return *this;
63 }
64 QH2Expected &operator=(Err &&error)
65 {
66 m_data = std::move(error);
67 return *this;
68 }
69 QH2Expected &operator=(const Err &error)
70 {
71 m_data = error;
72 return *this;
73 }
74 T unwrap() const
75 {
76 Q_ASSERT(ok());
77 return std::get<T>(m_data);
78 }
79 Err error() const
80 {
81 Q_ASSERT(has_error());
82 return std::get<Err>(m_data);
83 }
84 bool ok() const noexcept { return std::holds_alternative<T>(m_data); }
85 bool has_value() const noexcept { return ok(); }
86 bool has_error() const noexcept { return std::holds_alternative<Err>(m_data); }
87 void clear() noexcept { m_data.reset(); }
88
89private:
90 std::variant<T, Err> m_data;
91};
92
93class QHttp2Connection;
94class Q_NETWORK_EXPORT QHttp2Stream : public QObject
95{
96 Q_OBJECT
97 Q_DISABLE_COPY_MOVE(QHttp2Stream)
98
99public:
100 enum class State { Idle, ReservedRemote, Open, HalfClosedLocal, HalfClosedRemote, Closed };
101 Q_ENUM(State)
102 constexpr static quint8 DefaultPriority = 127;
103
104 struct Configuration
105 {
106 bool useDownloadBuffer = true;
107 bool useHeaderBuffer = true;
108 };
109
110 ~QHttp2Stream() noexcept;
111
112 // HTTP2 things
113 quint32 streamID() const noexcept { return m_streamID; }
114
115 // Are we waiting for a larger send window before sending more data?
116 bool isUploadBlocked() const noexcept;
117 bool isUploadingDATA() const noexcept { return m_uploadByteDevice != nullptr; }
118 State state() const noexcept { return m_state; }
119 bool isActive() const noexcept { return m_state != State::Closed && m_state != State::Idle; }
120 bool isPromisedStream() const noexcept { return m_isReserved; }
121 bool wasReset() const noexcept { return m_RST_STREAM_received.has_value() ||
122 m_RST_STREAM_sent.has_value(); }
123 bool wasResetbyPeer() const noexcept { return m_RST_STREAM_received.has_value(); }
124 quint32 RST_STREAMCodeReceived() const noexcept { return m_RST_STREAM_received.value_or(0); }
125 quint32 RST_STREAMCodeSent() const noexcept { return m_RST_STREAM_sent.value_or(0); }
126 // Just the list of headers, as received, may contain duplicates:
127 HPack::HttpHeader receivedHeaders() const noexcept { return m_headers; }
128
129 QByteDataBuffer downloadBuffer() const noexcept { return m_downloadBuffer; }
130 QByteDataBuffer takeDownloadBuffer() noexcept { return std::exchange(m_downloadBuffer, {}); }
131 void clearDownloadBuffer() { m_downloadBuffer.clear(); }
132
133 Configuration configuration() const { return m_configuration; }
134
135Q_SIGNALS:
136 void headersReceived(const HPack::HttpHeader &headers, bool endStream);
137 void headersUpdated();
138 void errorOccurred(Http2::Http2Error errorCode, const QString &errorString);
139 void stateChanged(QHttp2Stream::State newState);
140 void promisedStreamReceived(quint32 newStreamID);
141 void uploadBlocked();
142 void dataReceived(const QByteArray &data, bool endStream);
143 void rstFrameReceived(quint32 errorCode);
144
145 void bytesWritten(qint64 bytesWritten);
146 void uploadDeviceError(const QString &errorString);
147 void uploadFinished();
148
149public Q_SLOTS:
150 bool sendRST_STREAM(Http2::Http2Error errorCode);
151 bool sendHEADERS(const HPack::HttpHeader &headers, bool endStream,
152 quint8 priority = DefaultPriority);
153 bool sendDATA(const QByteArray &payload, bool endStream);
154 bool sendDATA(QIODevice *device, bool endStream);
155 bool sendDATA(QNonContiguousByteDevice *device, bool endStream);
156 void sendWINDOW_UPDATE(quint32 delta);
157
158private Q_SLOTS:
159 void maybeResumeUpload();
160 void uploadDeviceReadChannelFinished();
161 void uploadDeviceDestroyed();
162
163private:
164 friend class QHttp2Connection;
165 QHttp2Stream(QHttp2Connection *connection, quint32 streamID,
166 Configuration configuration) noexcept;
167
168 [[nodiscard]] QHttp2Connection *getConnection() const
169 {
170 return qobject_cast<QHttp2Connection *>(parent());
171 }
172
173 enum class StateTransition {
174 Open,
175 CloseLocal,
176 CloseRemote,
177 RST,
178 };
179
180 void setState(State newState);
181 void transitionState(StateTransition transition);
182 void internalSendDATA();
183 void finishSendDATA();
184
185 void handleDATA(const Http2::Frame &inboundFrame);
186 void handleHEADERS(Http2::FrameFlags frameFlags, const HPack::HttpHeader &headers);
187 void handleRST_STREAM(const Http2::Frame &inboundFrame);
188 void handleWINDOW_UPDATE(const Http2::Frame &inboundFrame);
189
190 void finishWithError(Http2::Http2Error errorCode, const QString &message);
191 void finishWithError(Http2::Http2Error errorCode);
192
193 void streamError(Http2::Http2Error errorCode, const QString &message);
194
195 // Keep it const since it never changes after creation
196 const quint32 m_streamID = 0;
197 qint32 m_recvWindow = 0;
198 qint32 m_sendWindow = 0;
199 bool m_endStreamAfterDATA = false;
200 std::optional<quint32> m_RST_STREAM_received;
201 std::optional<quint32> m_RST_STREAM_sent;
202
203 QIODevice *m_uploadDevice = nullptr;
204 QNonContiguousByteDevice *m_uploadByteDevice = nullptr;
205
206 QByteDataBuffer m_downloadBuffer;
207 State m_state = State::Idle;
208 HPack::HttpHeader m_headers;
209 bool m_isReserved = false;
210 bool m_owningByteDevice = false;
211
212 const Configuration m_configuration;
213
214 friend tst_QHttp2Connection;
215};
216
217class Q_NETWORK_EXPORT QHttp2Connection : public QObject
218{
219 Q_OBJECT
220 Q_DISABLE_COPY_MOVE(QHttp2Connection)
221
222public:
223 enum class CreateStreamError {
224 MaxConcurrentStreamsReached,
225 StreamIdsExhausted,
226 ReceivedGOAWAY,
227 UnknownError,
228 };
229 Q_ENUM(CreateStreamError)
230
231 enum class PingState {
232 Ping,
233 PongSignatureIdentical,
234 PongSignatureChanged,
235 PongNoPingSent, // We got an ACKed ping but had not sent any
236 };
237
238 // For a pre-established connection:
239 [[nodiscard]] static QHttp2Connection *
240 createUpgradedConnection(QIODevice *socket, const QHttp2Configuration &config);
241 // For a new connection, potential TLS handshake must already be finished:
242 [[nodiscard]] static QHttp2Connection *createDirectConnection(QIODevice *socket,
243 const QHttp2Configuration &config);
244 [[nodiscard]] static QHttp2Connection *
245 createDirectServerConnection(QIODevice *socket, const QHttp2Configuration &config);
246 ~QHttp2Connection();
247
248 [[nodiscard]] QH2Expected<QHttp2Stream *, CreateStreamError> createStream()
249 {
250 return createStream(QHttp2Stream::Configuration{});
251 }
252 [[nodiscard]] QH2Expected<QHttp2Stream *, CreateStreamError>
253 createStream(QHttp2Stream::Configuration config);
254
255 QHttp2Stream *getStream(quint32 streamId) const;
256 QHttp2Stream *promisedStream(const QUrl &streamKey) const
257 {
258 if (quint32 id = m_promisedStreams.value(streamKey, 0); id)
259 return m_streams.value(id);
260 return nullptr;
261 }
262
263 void close(Http2::Http2Error errorCode = Http2::HTTP2_NO_ERROR);
264
265 bool isGoingAway() const noexcept { return m_goingAway; }
266 // Set once a GOAWAY has been received; the peer did not process streams above this ID.
267 std::optional<quint32> lastGoAwayStreamID() const noexcept { return m_lastGoAwayLastStreamID; }
268
269 quint32 maxConcurrentStreams() const noexcept { return m_maxConcurrentStreams; }
270 quint32 peerMaxConcurrentStreams() const noexcept { return m_peerMaxConcurrentStreams; }
271
272 quint32 maxHeaderListSize() const noexcept { return m_maxHeaderListSize; }
273
274 bool isUpgradedConnection() const noexcept { return m_upgradedConnection; }
275
276 bool setSessionReceiveWindowSize(qint32 size);
277 quint64 totalBytesReceivedDATA() const noexcept { return m_totalBytesReceivedDATA; }
278
279Q_SIGNALS:
280 void newIncomingStream(QHttp2Stream *stream);
281 void newPromisedStream(QHttp2Stream *stream);
282 void errorReceived(/*@future: add as needed?*/); // Connection errors only, no stream-specific errors
283 void connectionClosed();
284 void settingsFrameReceived();
285 void pingFrameReceived(QHttp2Connection::PingState state);
286 void errorOccurred(Http2::Http2Error errorCode, const QString &errorString);
287 void receivedGOAWAY(Http2::Http2Error errorCode, quint32 lastStreamID);
288 void receivedEND_STREAM(quint32 streamID);
289 void incomingStreamErrorOccured(CreateStreamError error);
290
291public Q_SLOTS:
292 bool sendPing();
293 bool sendPing(QByteArrayView data);
294 void handleReadyRead();
295 void handleConnectionClosure();
296
297private:
298 friend class QHttp2Stream;
299 [[nodiscard]] QIODevice *getSocket() const { return qobject_cast<QIODevice *>(parent()); }
300
301 QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError>
302 createLocalStreamInternal(QHttp2Stream::Configuration = {});
303 QHttp2Stream *createStreamInternal_impl(quint32 streamID, QHttp2Stream::Configuration = {});
304
305 bool isInvalidStream(quint32 streamID) noexcept;
306 bool streamWasResetLocally(quint32 streamID) noexcept;
307 Q_ALWAYS_INLINE
308 bool streamIsIgnored(quint32 streamID) const noexcept;
309
310 void connectionError(Http2::Http2Error errorCode, const QString &message,
311 bool logAsError = true);
312 void setH2Configuration(QHttp2Configuration config);
313 void closeSession();
314 void registerStreamAsResetLocally(quint32 streamID);
315 qsizetype numActiveStreamsImpl(quint32 mask) const noexcept;
316 qsizetype numActiveRemoteStreams() const noexcept;
317 qsizetype numActiveLocalStreams() const noexcept;
318
319 bool sendClientPreface();
320 bool sendSETTINGS();
321 bool sendServerPreface();
322 bool serverCheckClientPreface();
323 bool sendWINDOW_UPDATE(quint32 streamID, quint32 delta);
324 void sendClientGracefulShutdownGoaway();
325 void sendInitialServerGracefulShutdownGoaway();
326 void sendFinalServerGracefulShutdownGoaway();
327 bool sendGOAWAYFrame(Http2::Http2Error errorCode, quint32 lastSreamID);
328 void maybeCloseOnGoingAway();
329 bool sendSETTINGS_ACK();
330
331 void handleDATA();
332 void handleHEADERS();
333 void handlePRIORITY();
334 void handleRST_STREAM();
335 void handleSETTINGS();
336 void handlePUSH_PROMISE();
337 void handlePING();
338 void handleGOAWAY();
339 void handleWINDOW_UPDATE();
340 void handleCONTINUATION();
341
342 void handleContinuedHEADERS();
343
344 bool validateHeaderListSize(const Http2::Frame &frame);
345
346 bool acceptSetting(Http2::Settings identifier, quint32 newValue);
347
348 bool readClientPreface();
349
350 explicit QHttp2Connection(QIODevice *socket);
351
352 enum class Type { Client, Server } m_connectionType = Type::Client;
353
354 bool waitingForSettingsACK = false;
355
356 static constexpr quint32 maxAcceptableTableSize = 16 * HPack::FieldLookupTable::DefaultSize;
357 // HTTP/2 4.3: Header compression is stateful. One compression context and
358 // one decompression context are used for the entire connection.
359 HPack::Decoder decoder = HPack::Decoder(HPack::FieldLookupTable::DefaultSize);
360 HPack::Encoder encoder = HPack::Encoder(HPack::FieldLookupTable::DefaultSize, true);
361
362 // If we receive SETTINGS_HEADER_TABLE_SIZE in a SETTINGS frame we have to perform a dynamic
363 // table size update on the _next_ HEADER block we send.
364 // Because this only happens on the next block we may have multiple pending updates, so we must
365 // notify of the _smallest_ one followed by the _final_ one. We keep them sorted in that order.
366 // @future: keep in mind if we add support for sending PUSH_PROMISE because it is a HEADER block
367 std::array<std::optional<quint32>, 2> pendingTableSizeUpdates;
368
369 QHttp2Configuration m_config;
370 QHash<quint32, QPointer<QHttp2Stream>> m_streams;
371 QSet<quint32> m_blockedStreams;
372 QHash<QUrl, quint32> m_promisedStreams;
373 QList<quint32> m_resetStreamIDs;
374
375 std::optional<QByteArray> m_lastPingSignature = std::nullopt;
376 quint32 m_nextStreamID = 1;
377
378 // Peer's max frame size (this min is the default value
379 // we start with, that can be updated by SETTINGS frame):
380 quint32 maxFrameSize = Http2::minPayloadLimit;
381
382 Http2::FrameReader frameReader;
383 Http2::Frame inboundFrame;
384 Http2::FrameWriter frameWriter;
385
386 // Temporary storage to assemble HEADERS' block
387 // from several CONTINUATION frames ...
388 bool continuationExpected = false;
389 std::vector<Http2::Frame> continuedFrames;
390 quint32 m_headerBlockSize = 0;
391
392 // Control flow:
393
394 // This is how many concurrent streams our peer allows us, 100 is the
395 // initial value, can be updated by the server's SETTINGS frame(s):
396 quint32 m_peerMaxConcurrentStreams = Http2::maxConcurrentStreams;
397 // While we allow sending SETTTINGS_MAX_CONCURRENT_STREAMS to limit our peer,
398 // it's just a hint and we do not actually enforce it (and we can continue
399 // sending requests and creating streams while maxConcurrentStreams allows).
400
401 // This is how many concurrent streams we allow our peer to create
402 // This value is specified in QHttp2Configuration when creating the connection
403 quint32 m_maxConcurrentStreams = Http2::maxConcurrentStreams;
404
405 // This is our (client-side) maximum possible receive window size, we set
406 // it in a ctor from QHttp2Configuration, it does not change after that.
407 // The default is 64Kb:
408 qint32 maxSessionReceiveWindowSize = Http2::defaultSessionWindowSize;
409
410 // Our session current receive window size, updated in a ctor from
411 // QHttp2Configuration. Signed integer since it can become negative
412 // (it's still a valid window size).
413 qint32 sessionReceiveWindowSize = Http2::defaultSessionWindowSize;
414 // Our per-stream receive window size, default is 64 Kb, will be updated
415 // from QHttp2Configuration. Again, signed - can become negative.
416 qint32 streamInitialReceiveWindowSize = Http2::defaultSessionWindowSize;
417
418 quint64 m_totalBytesReceivedDATA = 0;
419
420 // These are our peer's receive window sizes, they will be updated by the
421 // peer's SETTINGS and WINDOW_UPDATE frames, defaults presumed to be 64Kb.
422 qint32 sessionSendWindowSize = Http2::defaultSessionWindowSize;
423 qint32 streamInitialSendWindowSize = Http2::defaultSessionWindowSize;
424
425 // Our peer's header size limitations. It's unlimited by default, but can
426 // be changed via peer's SETTINGS frame.
427 quint32 m_maxHeaderListSize = (std::numeric_limits<quint32>::max)();
428 // The limit we advertise via SETTINGS_MAX_HEADER_LIST_SIZE is enforced on
429 // incoming header blocks both before HPACK decoding (compressed size) and
430 // during HPACK decoding (decoded size).
431
432 bool m_upgradedConnection = false;
433 bool m_goingAway = false;
434 bool pushPromiseEnabled = false;
435 quint32 m_lastIncomingStreamID = Http2::connectionStreamID;
436 // Gets lowered when/if we send GOAWAY:
437 quint32 m_lastStreamToProcess = Http2::lastValidStreamID;
438 static constexpr std::chrono::duration GoawayGracePeriod = std::chrono::seconds(60);
439 QDeadlineTimer m_goawayGraceTimer;
440
441 std::optional<quint32> m_lastGoAwayLastStreamID;
442 bool m_connectionAborted = false;
443
444 enum class GracefulShutdownState {
445 None,
446 AwaitingPriorPing,
447 AwaitingShutdownPing,
448 FinalGOAWAYSent,
449 };
450 GracefulShutdownState m_gracefulShutdownState = GracefulShutdownState::None;
451
452 bool m_prefaceSent = false;
453
454 // Server-side only:
455 bool m_waitingForClientPreface = false;
456
457 friend tst_QHttp2Connection;
458};
459
460QT_END_NAMESPACE
461
462#endif // HTTP2CONNECTION_P_H
\inmodule QtNetwork
\inmodule QtNetwork
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)