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 ~QHttp2Stream() noexcept;
105
106 // HTTP2 things
107 quint32 streamID() const noexcept { return m_streamID; }
108
109 // Are we waiting for a larger send window before sending more data?
110 bool isUploadBlocked() const noexcept;
111 bool isUploadingDATA() const noexcept { return m_uploadByteDevice != nullptr; }
112 State state() const noexcept { return m_state; }
113 bool isActive() const noexcept { return m_state != State::Closed && m_state != State::Idle; }
114 bool isPromisedStream() const noexcept { return m_isReserved; }
115 bool wasReset() const noexcept { return m_RST_STREAM_received.has_value() ||
116 m_RST_STREAM_sent.has_value(); }
117 bool wasResetbyPeer() const noexcept { return m_RST_STREAM_received.has_value(); }
118 quint32 RST_STREAMCodeReceived() const noexcept { return m_RST_STREAM_received.value_or(0); }
119 quint32 RST_STREAMCodeSent() const noexcept { return m_RST_STREAM_sent.value_or(0); }
120 // Just the list of headers, as received, may contain duplicates:
121 HPack::HttpHeader receivedHeaders() const noexcept { return m_headers; }
122
123 QByteDataBuffer downloadBuffer() const noexcept { return m_downloadBuffer; }
124 QByteDataBuffer takeDownloadBuffer() noexcept { return std::exchange(m_downloadBuffer, {}); }
125 void clearDownloadBuffer() { m_downloadBuffer.clear(); }
126
127Q_SIGNALS:
128 void headersReceived(const HPack::HttpHeader &headers, bool endStream);
129 void headersUpdated();
130 void errorOccurred(Http2::Http2Error errorCode, const QString &errorString);
131 void stateChanged(QHttp2Stream::State newState);
132 void promisedStreamReceived(quint32 newStreamID);
133 void uploadBlocked();
134 void dataReceived(const QByteArray &data, bool endStream);
135 void rstFrameReceived(quint32 errorCode);
136
137 void bytesWritten(qint64 bytesWritten);
138 void uploadDeviceError(const QString &errorString);
139 void uploadFinished();
140
141public Q_SLOTS:
142 bool sendRST_STREAM(Http2::Http2Error errorCode);
143 bool sendHEADERS(const HPack::HttpHeader &headers, bool endStream,
144 quint8 priority = DefaultPriority);
145 bool sendDATA(const QByteArray &payload, bool endStream);
146 bool sendDATA(QIODevice *device, bool endStream);
147 bool sendDATA(QNonContiguousByteDevice *device, bool endStream);
148 void sendWINDOW_UPDATE(quint32 delta);
149
150private Q_SLOTS:
151 void maybeResumeUpload();
152 void uploadDeviceReadChannelFinished();
153 void uploadDeviceDestroyed();
154
155private:
156 friend class QHttp2Connection;
157 QHttp2Stream(QHttp2Connection *connection, quint32 streamID) noexcept;
158
159 [[nodiscard]] QHttp2Connection *getConnection() const
160 {
161 return qobject_cast<QHttp2Connection *>(parent());
162 }
163
164 enum class StateTransition {
165 Open,
166 CloseLocal,
167 CloseRemote,
168 RST,
169 };
170
171 void setState(State newState);
172 void transitionState(StateTransition transition);
173 void internalSendDATA();
174 void finishSendDATA();
175
176 void handleDATA(const Http2::Frame &inboundFrame);
177 void handleHEADERS(Http2::FrameFlags frameFlags, const HPack::HttpHeader &headers);
178 void handleRST_STREAM(const Http2::Frame &inboundFrame);
179 void handleWINDOW_UPDATE(const Http2::Frame &inboundFrame);
180
181 void finishWithError(Http2::Http2Error errorCode, const QString &message);
182 void finishWithError(Http2::Http2Error errorCode);
183
184 void streamError(Http2::Http2Error errorCode,
185 QLatin1StringView message);
186
187 // Keep it const since it never changes after creation
188 const quint32 m_streamID = 0;
189 qint32 m_recvWindow = 0;
190 qint32 m_sendWindow = 0;
191 bool m_endStreamAfterDATA = false;
192 std::optional<quint32> m_RST_STREAM_received;
193 std::optional<quint32> m_RST_STREAM_sent;
194
195 QIODevice *m_uploadDevice = nullptr;
196 QNonContiguousByteDevice *m_uploadByteDevice = nullptr;
197
198 QByteDataBuffer m_downloadBuffer;
199 State m_state = State::Idle;
200 HPack::HttpHeader m_headers;
201 bool m_isReserved = false;
202 bool m_owningByteDevice = false;
203
204 friend tst_QHttp2Connection;
205};
206
207class Q_NETWORK_EXPORT QHttp2Connection : public QObject
208{
209 Q_OBJECT
210 Q_DISABLE_COPY_MOVE(QHttp2Connection)
211
212public:
213 enum class CreateStreamError {
214 MaxConcurrentStreamsReached,
215 StreamIdsExhausted,
216 ReceivedGOAWAY,
217 UnknownError,
218 };
219 Q_ENUM(CreateStreamError)
220
221 enum class PingState {
222 Ping,
223 PongSignatureIdentical,
224 PongSignatureChanged,
225 PongNoPingSent, // We got an ACKed ping but had not sent any
226 };
227
228 // For a pre-established connection:
229 [[nodiscard]] static QHttp2Connection *
230 createUpgradedConnection(QIODevice *socket, const QHttp2Configuration &config);
231 // For a new connection, potential TLS handshake must already be finished:
232 [[nodiscard]] static QHttp2Connection *createDirectConnection(QIODevice *socket,
233 const QHttp2Configuration &config);
234 [[nodiscard]] static QHttp2Connection *
235 createDirectServerConnection(QIODevice *socket, const QHttp2Configuration &config);
236 ~QHttp2Connection();
237
238 [[nodiscard]] QH2Expected<QHttp2Stream *, CreateStreamError> createStream();
239
240 QHttp2Stream *getStream(quint32 streamId) const;
241 QHttp2Stream *promisedStream(const QUrl &streamKey) const
242 {
243 if (quint32 id = m_promisedStreams.value(streamKey, 0); id)
244 return m_streams.value(id);
245 return nullptr;
246 }
247
248 void close(Http2::Http2Error error = Http2::HTTP2_NO_ERROR) { sendGOAWAY(error); }
249
250 bool isGoingAway() const noexcept { return m_goingAway; }
251
252 quint32 maxConcurrentStreams() const noexcept { return m_maxConcurrentStreams; }
253 quint32 peerMaxConcurrentStreams() const noexcept { return m_peerMaxConcurrentStreams; }
254
255 quint32 maxHeaderListSize() const noexcept { return m_maxHeaderListSize; }
256
257 bool isUpgradedConnection() const noexcept { return m_upgradedConnection; }
258
259Q_SIGNALS:
260 void newIncomingStream(QHttp2Stream *stream);
261 void newPromisedStream(QHttp2Stream *stream);
262 void errorReceived(/*@future: add as needed?*/); // Connection errors only, no stream-specific errors
263 void connectionClosed();
264 void settingsFrameReceived();
265 void pingFrameReceived(QHttp2Connection::PingState state);
266 void errorOccurred(Http2::Http2Error errorCode, const QString &errorString);
267 void receivedGOAWAY(Http2::Http2Error errorCode, quint32 lastStreamID);
268 void receivedEND_STREAM(quint32 streamID);
269 void incomingStreamErrorOccured(CreateStreamError error);
270
271public Q_SLOTS:
272 bool sendPing();
273 bool sendPing(QByteArrayView data);
274 void handleReadyRead();
275 void handleConnectionClosure();
276
277private:
278 friend class QHttp2Stream;
279 [[nodiscard]] QIODevice *getSocket() const { return qobject_cast<QIODevice *>(parent()); }
280
281 QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError> createLocalStreamInternal();
282 QHttp2Stream *createStreamInternal_impl(quint32 streamID);
283
284 bool isInvalidStream(quint32 streamID) noexcept;
285 bool streamWasResetLocally(quint32 streamID) noexcept;
286
287 void connectionError(Http2::Http2Error errorCode,
288 const char *message); // Connection failed to be established?
289 void setH2Configuration(QHttp2Configuration config);
290 void closeSession();
291 void registerStreamAsResetLocally(quint32 streamID);
292 qsizetype numActiveStreamsImpl(quint32 mask) const noexcept;
293 qsizetype numActiveRemoteStreams() const noexcept;
294 qsizetype numActiveLocalStreams() const noexcept;
295
296 bool sendClientPreface();
297 bool sendSETTINGS();
298 bool sendServerPreface();
299 bool serverCheckClientPreface();
300 bool sendWINDOW_UPDATE(quint32 streamID, quint32 delta);
301 bool sendGOAWAY(Http2::Http2Error errorCode);
302 bool sendSETTINGS_ACK();
303
304 void handleDATA();
305 void handleHEADERS();
306 void handlePRIORITY();
307 void handleRST_STREAM();
308 void handleSETTINGS();
309 void handlePUSH_PROMISE();
310 void handlePING();
311 void handleGOAWAY();
312 void handleWINDOW_UPDATE();
313 void handleCONTINUATION();
314
315 void handleContinuedHEADERS();
316
317 bool acceptSetting(Http2::Settings identifier, quint32 newValue);
318
319 bool readClientPreface();
320
321 explicit QHttp2Connection(QIODevice *socket);
322
323 enum class Type { Client, Server } m_connectionType = Type::Client;
324
325 bool waitingForSettingsACK = false;
326
327 static constexpr quint32 maxAcceptableTableSize = 16 * HPack::FieldLookupTable::DefaultSize;
328 // HTTP/2 4.3: Header compression is stateful. One compression context and
329 // one decompression context are used for the entire connection.
330 HPack::Decoder decoder = HPack::Decoder(HPack::FieldLookupTable::DefaultSize);
331 HPack::Encoder encoder = HPack::Encoder(HPack::FieldLookupTable::DefaultSize, true);
332
333 // If we receive SETTINGS_HEADER_TABLE_SIZE in a SETTINGS frame we have to perform a dynamic
334 // table size update on the _next_ HEADER block we send.
335 // Because this only happens on the next block we may have multiple pending updates, so we must
336 // notify of the _smallest_ one followed by the _final_ one. We keep them sorted in that order.
337 // @future: keep in mind if we add support for sending PUSH_PROMISE because it is a HEADER block
338 std::array<std::optional<quint32>, 2> pendingTableSizeUpdates;
339
340 QHttp2Configuration m_config;
341 QHash<quint32, QPointer<QHttp2Stream>> m_streams;
342 QSet<quint32> m_blockedStreams;
343 QHash<QUrl, quint32> m_promisedStreams;
344 QList<quint32> m_resetStreamIDs;
345
346 std::optional<QByteArray> m_lastPingSignature = std::nullopt;
347 quint32 m_nextStreamID = 1;
348
349 // Peer's max frame size (this min is the default value
350 // we start with, that can be updated by SETTINGS frame):
351 quint32 maxFrameSize = Http2::minPayloadLimit;
352
353 Http2::FrameReader frameReader;
354 Http2::Frame inboundFrame;
355 Http2::FrameWriter frameWriter;
356
357 // Temporary storage to assemble HEADERS' block
358 // from several CONTINUATION frames ...
359 bool continuationExpected = false;
360 std::vector<Http2::Frame> continuedFrames;
361
362 // Control flow:
363
364 // This is how many concurrent streams our peer allows us, 100 is the
365 // initial value, can be updated by the server's SETTINGS frame(s):
366 quint32 m_peerMaxConcurrentStreams = Http2::maxConcurrentStreams;
367 // While we allow sending SETTTINGS_MAX_CONCURRENT_STREAMS to limit our peer,
368 // it's just a hint and we do not actually enforce it (and we can continue
369 // sending requests and creating streams while maxConcurrentStreams allows).
370
371 // This is how many concurrent streams we allow our peer to create
372 // This value is specified in QHttp2Configuration when creating the connection
373 quint32 m_maxConcurrentStreams = Http2::maxConcurrentStreams;
374
375 // This is our (client-side) maximum possible receive window size, we set
376 // it in a ctor from QHttp2Configuration, it does not change after that.
377 // The default is 64Kb:
378 qint32 maxSessionReceiveWindowSize = Http2::defaultSessionWindowSize;
379
380 // Our session current receive window size, updated in a ctor from
381 // QHttp2Configuration. Signed integer since it can become negative
382 // (it's still a valid window size).
383 qint32 sessionReceiveWindowSize = Http2::defaultSessionWindowSize;
384 // Our per-stream receive window size, default is 64 Kb, will be updated
385 // from QHttp2Configuration. Again, signed - can become negative.
386 qint32 streamInitialReceiveWindowSize = Http2::defaultSessionWindowSize;
387
388 // These are our peer's receive window sizes, they will be updated by the
389 // peer's SETTINGS and WINDOW_UPDATE frames, defaults presumed to be 64Kb.
390 qint32 sessionSendWindowSize = Http2::defaultSessionWindowSize;
391 qint32 streamInitialSendWindowSize = Http2::defaultSessionWindowSize;
392
393 // Our peer's header size limitations. It's unlimited by default, but can
394 // be changed via peer's SETTINGS frame.
395 quint32 m_maxHeaderListSize = (std::numeric_limits<quint32>::max)();
396 // While we can send SETTINGS_MAX_HEADER_LIST_SIZE value (our limit on
397 // the headers size), we never enforce it, it's just a hint to our peer.
398
399 bool m_upgradedConnection = false;
400 bool m_goingAway = false;
401 bool pushPromiseEnabled = false;
402 quint32 m_lastIncomingStreamID = Http2::connectionStreamID;
403
404 bool m_prefaceSent = false;
405
406 // Server-side only:
407 bool m_waitingForClientPreface = false;
408
409 friend tst_QHttp2Connection;
410};
411
412QT_END_NAMESPACE
413
414#endif // HTTP2CONNECTION_P_H
\inmodule QtNetwork
\inmodule QtNetwork
Q_STATIC_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core")