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 };
108
109 ~QHttp2Stream() noexcept;
110
111 // HTTP2 things
112 quint32 streamID() const noexcept { return m_streamID; }
113
114 // Are we waiting for a larger send window before sending more data?
115 bool isUploadBlocked() const noexcept;
116 bool isUploadingDATA() const noexcept { return m_uploadByteDevice != nullptr; }
117 State state() const noexcept { return m_state; }
118 bool isActive() const noexcept { return m_state != State::Closed && m_state != State::Idle; }
119 bool isPromisedStream() const noexcept { return m_isReserved; }
120 bool wasReset() const noexcept { return m_RST_STREAM_received.has_value() ||
121 m_RST_STREAM_sent.has_value(); }
122 bool wasResetbyPeer() const noexcept { return m_RST_STREAM_received.has_value(); }
123 quint32 RST_STREAMCodeReceived() const noexcept { return m_RST_STREAM_received.value_or(0); }
124 quint32 RST_STREAMCodeSent() const noexcept { return m_RST_STREAM_sent.value_or(0); }
125 // Just the list of headers, as received, may contain duplicates:
126 HPack::HttpHeader receivedHeaders() const noexcept { return m_headers; }
127
128 QByteDataBuffer downloadBuffer() const noexcept { return m_downloadBuffer; }
129 QByteDataBuffer takeDownloadBuffer() noexcept { return std::exchange(m_downloadBuffer, {}); }
130 void clearDownloadBuffer() { m_downloadBuffer.clear(); }
131
132 Configuration configuration() const { return m_configuration; }
133
134Q_SIGNALS:
135 void headersReceived(const HPack::HttpHeader &headers, bool endStream);
136 void headersUpdated();
137 void errorOccurred(Http2::Http2Error errorCode, const QString &errorString);
138 void stateChanged(QHttp2Stream::State newState);
139 void promisedStreamReceived(quint32 newStreamID);
140 void uploadBlocked();
141 void dataReceived(const QByteArray &data, bool endStream);
142 void rstFrameReceived(quint32 errorCode);
143
144 void bytesWritten(qint64 bytesWritten);
145 void uploadDeviceError(const QString &errorString);
146 void uploadFinished();
147
148public Q_SLOTS:
149 bool sendRST_STREAM(Http2::Http2Error errorCode);
150 bool sendHEADERS(const HPack::HttpHeader &headers, bool endStream,
151 quint8 priority = DefaultPriority);
152 bool sendDATA(const QByteArray &payload, bool endStream);
153 bool sendDATA(QIODevice *device, bool endStream);
154 bool sendDATA(QNonContiguousByteDevice *device, bool endStream);
155 void sendWINDOW_UPDATE(quint32 delta);
156
157private Q_SLOTS:
158 void maybeResumeUpload();
159 void uploadDeviceReadChannelFinished();
160 void uploadDeviceDestroyed();
161
162private:
163 friend class QHttp2Connection;
164 QHttp2Stream(QHttp2Connection *connection, quint32 streamID,
165 Configuration configuration) noexcept;
166
167 [[nodiscard]] QHttp2Connection *getConnection() const
168 {
169 return qobject_cast<QHttp2Connection *>(parent());
170 }
171
172 enum class StateTransition {
173 Open,
174 CloseLocal,
175 CloseRemote,
176 RST,
177 };
178
179 void setState(State newState);
180 void transitionState(StateTransition transition);
181 void internalSendDATA();
182 void finishSendDATA();
183
184 void handleDATA(const Http2::Frame &inboundFrame);
185 void handleHEADERS(Http2::FrameFlags frameFlags, const HPack::HttpHeader &headers);
186 void handleRST_STREAM(const Http2::Frame &inboundFrame);
187 void handleWINDOW_UPDATE(const Http2::Frame &inboundFrame);
188
189 void finishWithError(Http2::Http2Error errorCode, const QString &message);
190 void finishWithError(Http2::Http2Error errorCode);
191
192 void streamError(Http2::Http2Error errorCode,
193 QLatin1StringView 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
267 quint32 maxConcurrentStreams() const noexcept { return m_maxConcurrentStreams; }
268 quint32 peerMaxConcurrentStreams() const noexcept { return m_peerMaxConcurrentStreams; }
269
270 quint32 maxHeaderListSize() const noexcept { return m_maxHeaderListSize; }
271
272 bool isUpgradedConnection() const noexcept { return m_upgradedConnection; }
273
274Q_SIGNALS:
275 void newIncomingStream(QHttp2Stream *stream);
276 void newPromisedStream(QHttp2Stream *stream);
277 void errorReceived(/*@future: add as needed?*/); // Connection errors only, no stream-specific errors
278 void connectionClosed();
279 void settingsFrameReceived();
280 void pingFrameReceived(QHttp2Connection::PingState state);
281 void errorOccurred(Http2::Http2Error errorCode, const QString &errorString);
282 void receivedGOAWAY(Http2::Http2Error errorCode, quint32 lastStreamID);
283 void receivedEND_STREAM(quint32 streamID);
284 void incomingStreamErrorOccured(CreateStreamError error);
285
286public Q_SLOTS:
287 bool sendPing();
288 bool sendPing(QByteArrayView data);
289 void handleReadyRead();
290 void handleConnectionClosure();
291
292private:
293 friend class QHttp2Stream;
294 [[nodiscard]] QIODevice *getSocket() const { return qobject_cast<QIODevice *>(parent()); }
295
296 QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError>
297 createLocalStreamInternal(QHttp2Stream::Configuration = {});
298 QHttp2Stream *createStreamInternal_impl(quint32 streamID, QHttp2Stream::Configuration = {});
299
300 bool isInvalidStream(quint32 streamID) noexcept;
301 bool streamWasResetLocally(quint32 streamID) noexcept;
302 Q_ALWAYS_INLINE
303 bool streamIsIgnored(quint32 streamID) const noexcept;
304
305 void connectionError(Http2::Http2Error errorCode, const char *message, bool logAsError = true);
306 void setH2Configuration(QHttp2Configuration config);
307 void closeSession();
308 void registerStreamAsResetLocally(quint32 streamID);
309 qsizetype numActiveStreamsImpl(quint32 mask) const noexcept;
310 qsizetype numActiveRemoteStreams() const noexcept;
311 qsizetype numActiveLocalStreams() const noexcept;
312
313 bool sendClientPreface();
314 bool sendSETTINGS();
315 bool sendServerPreface();
316 bool serverCheckClientPreface();
317 bool sendWINDOW_UPDATE(quint32 streamID, quint32 delta);
318 void sendClientGracefulShutdownGoaway();
319 void sendInitialServerGracefulShutdownGoaway();
320 void sendFinalServerGracefulShutdownGoaway();
321 bool sendGOAWAYFrame(Http2::Http2Error errorCode, quint32 lastSreamID);
322 void maybeCloseOnGoingAway();
323 bool sendSETTINGS_ACK();
324
325 void handleDATA();
326 void handleHEADERS();
327 void handlePRIORITY();
328 void handleRST_STREAM();
329 void handleSETTINGS();
330 void handlePUSH_PROMISE();
331 void handlePING();
332 void handleGOAWAY();
333 void handleWINDOW_UPDATE();
334 void handleCONTINUATION();
335
336 void handleContinuedHEADERS();
337
338 bool acceptSetting(Http2::Settings identifier, quint32 newValue);
339
340 bool readClientPreface();
341
342 explicit QHttp2Connection(QIODevice *socket);
343
344 enum class Type { Client, Server } m_connectionType = Type::Client;
345
346 bool waitingForSettingsACK = false;
347
348 static constexpr quint32 maxAcceptableTableSize = 16 * HPack::FieldLookupTable::DefaultSize;
349 // HTTP/2 4.3: Header compression is stateful. One compression context and
350 // one decompression context are used for the entire connection.
351 HPack::Decoder decoder = HPack::Decoder(HPack::FieldLookupTable::DefaultSize);
352 HPack::Encoder encoder = HPack::Encoder(HPack::FieldLookupTable::DefaultSize, true);
353
354 // If we receive SETTINGS_HEADER_TABLE_SIZE in a SETTINGS frame we have to perform a dynamic
355 // table size update on the _next_ HEADER block we send.
356 // Because this only happens on the next block we may have multiple pending updates, so we must
357 // notify of the _smallest_ one followed by the _final_ one. We keep them sorted in that order.
358 // @future: keep in mind if we add support for sending PUSH_PROMISE because it is a HEADER block
359 std::array<std::optional<quint32>, 2> pendingTableSizeUpdates;
360
361 QHttp2Configuration m_config;
362 QHash<quint32, QPointer<QHttp2Stream>> m_streams;
363 QSet<quint32> m_blockedStreams;
364 QHash<QUrl, quint32> m_promisedStreams;
365 QList<quint32> m_resetStreamIDs;
366
367 std::optional<QByteArray> m_lastPingSignature = std::nullopt;
368 quint32 m_nextStreamID = 1;
369
370 // Peer's max frame size (this min is the default value
371 // we start with, that can be updated by SETTINGS frame):
372 quint32 maxFrameSize = Http2::minPayloadLimit;
373
374 Http2::FrameReader frameReader;
375 Http2::Frame inboundFrame;
376 Http2::FrameWriter frameWriter;
377
378 // Temporary storage to assemble HEADERS' block
379 // from several CONTINUATION frames ...
380 bool continuationExpected = false;
381 std::vector<Http2::Frame> continuedFrames;
382
383 // Control flow:
384
385 // This is how many concurrent streams our peer allows us, 100 is the
386 // initial value, can be updated by the server's SETTINGS frame(s):
387 quint32 m_peerMaxConcurrentStreams = Http2::maxConcurrentStreams;
388 // While we allow sending SETTTINGS_MAX_CONCURRENT_STREAMS to limit our peer,
389 // it's just a hint and we do not actually enforce it (and we can continue
390 // sending requests and creating streams while maxConcurrentStreams allows).
391
392 // This is how many concurrent streams we allow our peer to create
393 // This value is specified in QHttp2Configuration when creating the connection
394 quint32 m_maxConcurrentStreams = Http2::maxConcurrentStreams;
395
396 // This is our (client-side) maximum possible receive window size, we set
397 // it in a ctor from QHttp2Configuration, it does not change after that.
398 // The default is 64Kb:
399 qint32 maxSessionReceiveWindowSize = Http2::defaultSessionWindowSize;
400
401 // Our session current receive window size, updated in a ctor from
402 // QHttp2Configuration. Signed integer since it can become negative
403 // (it's still a valid window size).
404 qint32 sessionReceiveWindowSize = Http2::defaultSessionWindowSize;
405 // Our per-stream receive window size, default is 64 Kb, will be updated
406 // from QHttp2Configuration. Again, signed - can become negative.
407 qint32 streamInitialReceiveWindowSize = Http2::defaultSessionWindowSize;
408
409 // These are our peer's receive window sizes, they will be updated by the
410 // peer's SETTINGS and WINDOW_UPDATE frames, defaults presumed to be 64Kb.
411 qint32 sessionSendWindowSize = Http2::defaultSessionWindowSize;
412 qint32 streamInitialSendWindowSize = Http2::defaultSessionWindowSize;
413
414 // Our peer's header size limitations. It's unlimited by default, but can
415 // be changed via peer's SETTINGS frame.
416 quint32 m_maxHeaderListSize = (std::numeric_limits<quint32>::max)();
417 // While we can send SETTINGS_MAX_HEADER_LIST_SIZE value (our limit on
418 // the headers size), we never enforce it, it's just a hint to our peer.
419
420 bool m_upgradedConnection = false;
421 bool m_goingAway = false;
422 bool pushPromiseEnabled = false;
423 quint32 m_lastIncomingStreamID = Http2::connectionStreamID;
424 // Gets lowered when/if we send GOAWAY:
425 quint32 m_lastStreamToProcess = Http2::lastValidStreamID;
426 static constexpr std::chrono::duration GoawayGracePeriod = std::chrono::seconds(60);
427 QDeadlineTimer m_goawayGraceTimer;
428
429 std::optional<quint32> m_lastGoAwayLastStreamID;
430 bool m_connectionAborted = false;
431
432 enum class GracefulShutdownState {
433 None,
434 AwaitingPriorPing,
435 AwaitingShutdownPing,
436 FinalGOAWAYSent,
437 };
438 GracefulShutdownState m_gracefulShutdownState = GracefulShutdownState::None;
439
440 bool m_prefaceSent = false;
441
442 // Server-side only:
443 bool m_waitingForClientPreface = false;
444
445 friend tst_QHttp2Connection;
446};
447
448QT_END_NAMESPACE
449
450#endif // HTTP2CONNECTION_P_H
\inmodule QtNetwork
\inmodule QtNetwork
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)