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,
194 QLatin1StringView message);
195
196 // Keep it const since it never changes after creation
197 const quint32 m_streamID = 0;
198 qint32 m_recvWindow = 0;
199 qint32 m_sendWindow = 0;
200 bool m_endStreamAfterDATA = false;
201 std::optional<quint32> m_RST_STREAM_received;
202 std::optional<quint32> m_RST_STREAM_sent;
203
204 QIODevice *m_uploadDevice = nullptr;
205 QNonContiguousByteDevice *m_uploadByteDevice = nullptr;
206
207 QByteDataBuffer m_downloadBuffer;
208 State m_state = State::Idle;
209 HPack::HttpHeader m_headers;
210 bool m_isReserved = false;
211 bool m_owningByteDevice = false;
212
213 const Configuration m_configuration;
214
215 friend tst_QHttp2Connection;
216};
217
218class Q_NETWORK_EXPORT QHttp2Connection : public QObject
219{
220 Q_OBJECT
221 Q_DISABLE_COPY_MOVE(QHttp2Connection)
222
223public:
224 enum class CreateStreamError {
225 MaxConcurrentStreamsReached,
226 StreamIdsExhausted,
227 ReceivedGOAWAY,
228 UnknownError,
229 };
230 Q_ENUM(CreateStreamError)
231
232 enum class PingState {
233 Ping,
234 PongSignatureIdentical,
235 PongSignatureChanged,
236 PongNoPingSent, // We got an ACKed ping but had not sent any
237 };
238
239 // For a pre-established connection:
240 [[nodiscard]] static QHttp2Connection *
241 createUpgradedConnection(QIODevice *socket, const QHttp2Configuration &config);
242 // For a new connection, potential TLS handshake must already be finished:
243 [[nodiscard]] static QHttp2Connection *createDirectConnection(QIODevice *socket,
244 const QHttp2Configuration &config);
245 [[nodiscard]] static QHttp2Connection *
246 createDirectServerConnection(QIODevice *socket, const QHttp2Configuration &config);
247 ~QHttp2Connection();
248
249 [[nodiscard]] QH2Expected<QHttp2Stream *, CreateStreamError> createStream()
250 {
251 return createStream(QHttp2Stream::Configuration{});
252 }
253 [[nodiscard]] QH2Expected<QHttp2Stream *, CreateStreamError>
254 createStream(QHttp2Stream::Configuration config);
255
256 QHttp2Stream *getStream(quint32 streamId) const;
257 QHttp2Stream *promisedStream(const QUrl &streamKey) const
258 {
259 if (quint32 id = m_promisedStreams.value(streamKey, 0); id)
260 return m_streams.value(id);
261 return nullptr;
262 }
263
264 void close(Http2::Http2Error errorCode = Http2::HTTP2_NO_ERROR);
265
266 bool isGoingAway() const noexcept { return m_goingAway; }
267
268 quint32 maxConcurrentStreams() const noexcept { return m_maxConcurrentStreams; }
269 quint32 peerMaxConcurrentStreams() const noexcept { return m_peerMaxConcurrentStreams; }
270
271 quint32 maxHeaderListSize() const noexcept { return m_maxHeaderListSize; }
272
273 bool isUpgradedConnection() const noexcept { return m_upgradedConnection; }
274
275 bool setSessionReceiveWindowSize(qint32 size);
276 quint64 totalBytesReceivedDATA() const noexcept { return m_totalBytesReceivedDATA; }
277
278Q_SIGNALS:
279 void newIncomingStream(QHttp2Stream *stream);
280 void newPromisedStream(QHttp2Stream *stream);
281 void errorReceived(/*@future: add as needed?*/); // Connection errors only, no stream-specific errors
282 void connectionClosed();
283 void settingsFrameReceived();
284 void pingFrameReceived(QHttp2Connection::PingState state);
285 void errorOccurred(Http2::Http2Error errorCode, const QString &errorString);
286 void receivedGOAWAY(Http2::Http2Error errorCode, quint32 lastStreamID);
287 void receivedEND_STREAM(quint32 streamID);
288 void incomingStreamErrorOccured(CreateStreamError error);
289
290public Q_SLOTS:
291 bool sendPing();
292 bool sendPing(QByteArrayView data);
293 void handleReadyRead();
294 void handleConnectionClosure();
295
296private:
297 friend class QHttp2Stream;
298 [[nodiscard]] QIODevice *getSocket() const { return qobject_cast<QIODevice *>(parent()); }
299
300 QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError>
301 createLocalStreamInternal(QHttp2Stream::Configuration = {});
302 QHttp2Stream *createStreamInternal_impl(quint32 streamID, QHttp2Stream::Configuration = {});
303
304 bool isInvalidStream(quint32 streamID) noexcept;
305 bool streamWasResetLocally(quint32 streamID) noexcept;
306 Q_ALWAYS_INLINE
307 bool streamIsIgnored(quint32 streamID) const noexcept;
308
309 void connectionError(Http2::Http2Error errorCode, const char *message, bool logAsError = true);
310 void setH2Configuration(QHttp2Configuration config);
311 void closeSession();
312 void registerStreamAsResetLocally(quint32 streamID);
313 qsizetype numActiveStreamsImpl(quint32 mask) const noexcept;
314 qsizetype numActiveRemoteStreams() const noexcept;
315 qsizetype numActiveLocalStreams() const noexcept;
316
317 bool sendClientPreface();
318 bool sendSETTINGS();
319 bool sendServerPreface();
320 bool serverCheckClientPreface();
321 bool sendWINDOW_UPDATE(quint32 streamID, quint32 delta);
322 void sendClientGracefulShutdownGoaway();
323 void sendInitialServerGracefulShutdownGoaway();
324 void sendFinalServerGracefulShutdownGoaway();
325 bool sendGOAWAYFrame(Http2::Http2Error errorCode, quint32 lastSreamID);
326 void maybeCloseOnGoingAway();
327 bool sendSETTINGS_ACK();
328
329 void handleDATA();
330 void handleHEADERS();
331 void handlePRIORITY();
332 void handleRST_STREAM();
333 void handleSETTINGS();
334 void handlePUSH_PROMISE();
335 void handlePING();
336 void handleGOAWAY();
337 void handleWINDOW_UPDATE();
338 void handleCONTINUATION();
339
340 void handleContinuedHEADERS();
341
342 bool validateHeaderListSize(const Http2::Frame &frame);
343
344 bool acceptSetting(Http2::Settings identifier, quint32 newValue);
345
346 bool readClientPreface();
347
348 explicit QHttp2Connection(QIODevice *socket);
349
350 enum class Type { Client, Server } m_connectionType = Type::Client;
351
352 bool waitingForSettingsACK = false;
353
354 static constexpr quint32 maxAcceptableTableSize = 16 * HPack::FieldLookupTable::DefaultSize;
355 // HTTP/2 4.3: Header compression is stateful. One compression context and
356 // one decompression context are used for the entire connection.
357 HPack::Decoder decoder = HPack::Decoder(HPack::FieldLookupTable::DefaultSize);
358 HPack::Encoder encoder = HPack::Encoder(HPack::FieldLookupTable::DefaultSize, true);
359
360 // If we receive SETTINGS_HEADER_TABLE_SIZE in a SETTINGS frame we have to perform a dynamic
361 // table size update on the _next_ HEADER block we send.
362 // Because this only happens on the next block we may have multiple pending updates, so we must
363 // notify of the _smallest_ one followed by the _final_ one. We keep them sorted in that order.
364 // @future: keep in mind if we add support for sending PUSH_PROMISE because it is a HEADER block
365 std::array<std::optional<quint32>, 2> pendingTableSizeUpdates;
366
367 QHttp2Configuration m_config;
368 QHash<quint32, QPointer<QHttp2Stream>> m_streams;
369 QSet<quint32> m_blockedStreams;
370 QHash<QUrl, quint32> m_promisedStreams;
371 QList<quint32> m_resetStreamIDs;
372
373 std::optional<QByteArray> m_lastPingSignature = std::nullopt;
374 quint32 m_nextStreamID = 1;
375
376 // Peer's max frame size (this min is the default value
377 // we start with, that can be updated by SETTINGS frame):
378 quint32 maxFrameSize = Http2::minPayloadLimit;
379
380 Http2::FrameReader frameReader;
381 Http2::Frame inboundFrame;
382 Http2::FrameWriter frameWriter;
383
384 // Temporary storage to assemble HEADERS' block
385 // from several CONTINUATION frames ...
386 bool continuationExpected = false;
387 std::vector<Http2::Frame> continuedFrames;
388 quint32 m_headerBlockSize = 0;
389
390 // Control flow:
391
392 // This is how many concurrent streams our peer allows us, 100 is the
393 // initial value, can be updated by the server's SETTINGS frame(s):
394 quint32 m_peerMaxConcurrentStreams = Http2::maxConcurrentStreams;
395 // While we allow sending SETTTINGS_MAX_CONCURRENT_STREAMS to limit our peer,
396 // it's just a hint and we do not actually enforce it (and we can continue
397 // sending requests and creating streams while maxConcurrentStreams allows).
398
399 // This is how many concurrent streams we allow our peer to create
400 // This value is specified in QHttp2Configuration when creating the connection
401 quint32 m_maxConcurrentStreams = Http2::maxConcurrentStreams;
402
403 // This is our (client-side) maximum possible receive window size, we set
404 // it in a ctor from QHttp2Configuration, it does not change after that.
405 // The default is 64Kb:
406 qint32 maxSessionReceiveWindowSize = Http2::defaultSessionWindowSize;
407
408 // Our session current receive window size, updated in a ctor from
409 // QHttp2Configuration. Signed integer since it can become negative
410 // (it's still a valid window size).
411 qint32 sessionReceiveWindowSize = Http2::defaultSessionWindowSize;
412 // Our per-stream receive window size, default is 64 Kb, will be updated
413 // from QHttp2Configuration. Again, signed - can become negative.
414 qint32 streamInitialReceiveWindowSize = Http2::defaultSessionWindowSize;
415
416 quint64 m_totalBytesReceivedDATA = 0;
417
418 // These are our peer's receive window sizes, they will be updated by the
419 // peer's SETTINGS and WINDOW_UPDATE frames, defaults presumed to be 64Kb.
420 qint32 sessionSendWindowSize = Http2::defaultSessionWindowSize;
421 qint32 streamInitialSendWindowSize = Http2::defaultSessionWindowSize;
422
423 // Our peer's header size limitations. It's unlimited by default, but can
424 // be changed via peer's SETTINGS frame.
425 quint32 m_maxHeaderListSize = (std::numeric_limits<quint32>::max)();
426 // The limit we advertise via SETTINGS_MAX_HEADER_LIST_SIZE is enforced on
427 // incoming header blocks both before HPACK decoding (compressed size) and
428 // during HPACK decoding (decoded size).
429
430 bool m_upgradedConnection = false;
431 bool m_goingAway = false;
432 bool pushPromiseEnabled = false;
433 quint32 m_lastIncomingStreamID = Http2::connectionStreamID;
434 // Gets lowered when/if we send GOAWAY:
435 quint32 m_lastStreamToProcess = Http2::lastValidStreamID;
436 static constexpr std::chrono::duration GoawayGracePeriod = std::chrono::seconds(60);
437 QDeadlineTimer m_goawayGraceTimer;
438
439 std::optional<quint32> m_lastGoAwayLastStreamID;
440 bool m_connectionAborted = false;
441
442 enum class GracefulShutdownState {
443 None,
444 AwaitingPriorPing,
445 AwaitingShutdownPing,
446 FinalGOAWAYSent,
447 };
448 GracefulShutdownState m_gracefulShutdownState = GracefulShutdownState::None;
449
450 bool m_prefaceSent = false;
451
452 // Server-side only:
453 bool m_waitingForClientPreface = false;
454
455 friend tst_QHttp2Connection;
456};
457
458QT_END_NAMESPACE
459
460#endif // HTTP2CONNECTION_P_H
\inmodule QtNetwork
\inmodule QtNetwork
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)