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
4#ifndef HTTP2CONNECTION_P_H
5#define HTTP2CONNECTION_P_H
6
7//
8// W A R N I N G
9// -------------
10//
11// This file is not part of the Qt API. It exists for the convenience
12// of the Network Access API. This header file may change from
13// version to version without notice, or even be removed.
14//
15// We mean it.
16//
17
18#include <private/qtnetworkglobal_p.h>
19
20#include <QtCore/qobject.h>
21#include <QtCore/qhash.h>
22#include <QtCore/qset.h>
23#include <QtCore/qvarlengtharray.h>
24#include <QtCore/qxpfunctional.h>
25#include <QtNetwork/qhttp2configuration.h>
26#include <QtNetwork/qtcpsocket.h>
27
28#include <private/http2protocol_p.h>
29#include <private/http2streams_p.h>
30#include <private/http2frames_p.h>
31#include <private/hpack_p.h>
32
33#include <variant>
34#include <optional>
35#include <type_traits>
36#include <limits>
37
38class tst_QHttp2Connection;
39
40QT_BEGIN_NAMESPACE
41
42template <typename T, typename Err>
43class QH2Expected
44{
45 static_assert(!std::is_same_v<T, Err>, "T and Err must be different types");
46public:
47 // Rule Of Zero applies
48 QH2Expected(T &&value) : m_data(std::move(value)) { }
49 QH2Expected(const T &value) : m_data(value) { }
50 QH2Expected(Err &&error) : m_data(std::move(error)) { }
51 QH2Expected(const Err &error) : m_data(error) { }
52
53 QH2Expected &operator=(T &&value)
54 {
55 m_data = std::move(value);
56 return *this;
57 }
58 QH2Expected &operator=(const T &value)
59 {
60 m_data = value;
61 return *this;
62 }
63 QH2Expected &operator=(Err &&error)
64 {
65 m_data = std::move(error);
66 return *this;
67 }
68 QH2Expected &operator=(const Err &error)
69 {
70 m_data = error;
71 return *this;
72 }
73 T unwrap() const
74 {
75 Q_ASSERT(ok());
76 return std::get<T>(m_data);
77 }
78 Err error() const
79 {
80 Q_ASSERT(has_error());
81 return std::get<Err>(m_data);
82 }
83 bool ok() const noexcept { return std::holds_alternative<T>(m_data); }
84 bool has_value() const noexcept { return ok(); }
85 bool has_error() const noexcept { return std::holds_alternative<Err>(m_data); }
86 void clear() noexcept { m_data.reset(); }
87
88private:
89 std::variant<T, Err> m_data;
90};
91
92class QHttp2Connection;
93class Q_NETWORK_EXPORT QHttp2Stream : public QObject
94{
95 Q_OBJECT
96 Q_DISABLE_COPY_MOVE(QHttp2Stream)
97
98public:
99 enum class State { Idle, ReservedRemote, Open, HalfClosedLocal, HalfClosedRemote, Closed };
100 Q_ENUM(State)
101 constexpr static quint8 DefaultPriority = 127;
102
103 ~QHttp2Stream() noexcept;
104
105 // HTTP2 things
106 quint32 streamID() const noexcept { return m_streamID; }
107
108 // Are we waiting for a larger send window before sending more data?
109 bool isUploadBlocked() const noexcept;
110 bool isUploadingDATA() const noexcept { return m_uploadByteDevice != nullptr; }
111 State state() const noexcept { return m_state; }
112 bool isActive() const noexcept { return m_state != State::Closed && m_state != State::Idle; }
113 bool isPromisedStream() const noexcept { return m_isReserved; }
114 bool wasReset() const noexcept { return m_RST_STREAM_received.has_value() ||
115 m_RST_STREAM_sent.has_value(); }
116 bool wasResetbyPeer() const noexcept { return m_RST_STREAM_received.has_value(); }
117 quint32 RST_STREAMCodeReceived() const noexcept { return m_RST_STREAM_received.value_or(u: 0); }
118 quint32 RST_STREAMCodeSent() const noexcept { return m_RST_STREAM_sent.value_or(u: 0); }
119 // Just the list of headers, as received, may contain duplicates:
120 HPack::HttpHeader receivedHeaders() const noexcept { return m_headers; }
121
122 QByteDataBuffer downloadBuffer() const noexcept { return m_downloadBuffer; }
123 QByteDataBuffer takeDownloadBuffer() noexcept { return std::exchange(obj&: m_downloadBuffer, new_val: {}); }
124 void clearDownloadBuffer() { m_downloadBuffer.clear(); }
125
126Q_SIGNALS:
127 void headersReceived(const HPack::HttpHeader &headers, bool endStream);
128 void headersUpdated();
129 void errorOccurred(Http2::Http2Error errorCode, const QString &errorString);
130 void stateChanged(QHttp2Stream::State newState);
131 void promisedStreamReceived(quint32 newStreamID);
132 void uploadBlocked();
133 void dataReceived(const QByteArray &data, bool endStream);
134 void rstFrameRecived(quint32 errorCode);
135
136 void bytesWritten(qint64 bytesWritten);
137 void uploadDeviceError(const QString &errorString);
138 void uploadFinished();
139
140public Q_SLOTS:
141 bool sendRST_STREAM(Http2::Http2Error errorCode);
142 bool sendHEADERS(const HPack::HttpHeader &headers, bool endStream,
143 quint8 priority = DefaultPriority);
144 void sendDATA(QIODevice *device, bool endStream);
145 void sendDATA(QNonContiguousByteDevice *device, bool endStream);
146 void sendWINDOW_UPDATE(quint32 delta);
147
148private Q_SLOTS:
149 void maybeResumeUpload();
150 void uploadDeviceReadChannelFinished();
151 void uploadDeviceDestroyed();
152
153private:
154 friend class QHttp2Connection;
155 QHttp2Stream(QHttp2Connection *connection, quint32 streamID) noexcept;
156
157 [[nodiscard]] QHttp2Connection *getConnection() const
158 {
159 return qobject_cast<QHttp2Connection *>(object: parent());
160 }
161
162 enum class StateTransition {
163 Open,
164 CloseLocal,
165 CloseRemote,
166 RST,
167 };
168
169 void setState(State newState);
170 void transitionState(StateTransition transition);
171 void internalSendDATA();
172 void finishSendDATA();
173
174 void handleDATA(const Http2::Frame &inboundFrame);
175 void handleHEADERS(Http2::FrameFlags frameFlags, const HPack::HttpHeader &headers);
176 void handleRST_STREAM(const Http2::Frame &inboundFrame);
177 void handleWINDOW_UPDATE(const Http2::Frame &inboundFrame);
178
179 void finishWithError(Http2::Http2Error errorCode, const QString &message);
180 void finishWithError(Http2::Http2Error errorCode);
181
182 void streamError(Http2::Http2Error errorCode,
183 QLatin1StringView message);
184
185 // Keep it const since it never changes after creation
186 const quint32 m_streamID = 0;
187 qint32 m_recvWindow = 0;
188 qint32 m_sendWindow = 0;
189 bool m_endStreamAfterDATA = false;
190 std::optional<quint32> m_RST_STREAM_received;
191 std::optional<quint32> m_RST_STREAM_sent;
192
193 QIODevice *m_uploadDevice = nullptr;
194 QNonContiguousByteDevice *m_uploadByteDevice = nullptr;
195
196 QByteDataBuffer m_downloadBuffer;
197 State m_state = State::Idle;
198 HPack::HttpHeader m_headers;
199 bool m_isReserved = false;
200 bool m_owningByteDevice = false;
201
202 friend tst_QHttp2Connection;
203};
204
205class Q_NETWORK_EXPORT QHttp2Connection : public QObject
206{
207 Q_OBJECT
208 Q_DISABLE_COPY_MOVE(QHttp2Connection)
209
210public:
211 enum class CreateStreamError {
212 MaxConcurrentStreamsReached,
213 StreamIdsExhausted,
214 ReceivedGOAWAY,
215 UnknownError,
216 };
217 Q_ENUM(CreateStreamError)
218
219 enum class PingState {
220 Ping,
221 PongSignatureIdentical,
222 PongSignatureChanged,
223 PongNoPingSent, // We got an ACKed ping but had not sent any
224 };
225
226 // For a pre-established connection:
227 [[nodiscard]] static QHttp2Connection *
228 createUpgradedConnection(QIODevice *socket, const QHttp2Configuration &config);
229 // For a new connection, potential TLS handshake must already be finished:
230 [[nodiscard]] static QHttp2Connection *createDirectConnection(QIODevice *socket,
231 const QHttp2Configuration &config);
232 [[nodiscard]] static QHttp2Connection *
233 createDirectServerConnection(QIODevice *socket, const QHttp2Configuration &config);
234 ~QHttp2Connection();
235
236 [[nodiscard]] QH2Expected<QHttp2Stream *, CreateStreamError> createStream();
237
238 QHttp2Stream *getStream(quint32 streamId) const;
239 QHttp2Stream *promisedStream(const QUrl &streamKey) const
240 {
241 if (quint32 id = m_promisedStreams.value(key: streamKey, defaultValue: 0); id)
242 return m_streams.value(key: id);
243 return nullptr;
244 }
245
246 void close() { sendGOAWAY(errorCode: Http2::HTTP2_NO_ERROR); }
247
248 bool isGoingAway() const noexcept { return m_goingAway; }
249
250 quint32 maxConcurrentStreams() const noexcept { return m_maxConcurrentStreams; }
251 quint32 maxHeaderListSize() const noexcept { return m_maxHeaderListSize; }
252
253 bool isUpgradedConnection() const noexcept { return m_upgradedConnection; }
254
255Q_SIGNALS:
256 void newIncomingStream(QHttp2Stream *stream);
257 void newPromisedStream(QHttp2Stream *stream);
258 void errorReceived(/*@future: add as needed?*/); // Connection errors only, no stream-specific errors
259 void connectionClosed();
260 void settingsFrameReceived();
261 void pingFrameRecived(QHttp2Connection::PingState state);
262 void errorOccurred(Http2::Http2Error errorCode, const QString &errorString);
263 void receivedGOAWAY(Http2::Http2Error errorCode, quint32 lastStreamID);
264 void receivedEND_STREAM(quint32 streamID);
265public Q_SLOTS:
266 bool sendPing();
267 bool sendPing(QByteArrayView data);
268 void handleReadyRead();
269 void handleConnectionClosure();
270
271private:
272 friend class QHttp2Stream;
273 [[nodiscard]] QIODevice *getSocket() const { return qobject_cast<QIODevice *>(object: parent()); }
274
275 QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError> createStreamInternal();
276 QHttp2Stream *createStreamInternal_impl(quint32 streamID);
277
278 bool isInvalidStream(quint32 streamID) noexcept;
279 bool streamWasResetLocally(quint32 streamID) noexcept;
280
281 void connectionError(Http2::Http2Error errorCode,
282 const char *message); // Connection failed to be established?
283 void setH2Configuration(QHttp2Configuration config);
284 void closeSession();
285 void registerStreamAsResetLocally(quint32 streamID);
286 qsizetype numActiveStreamsImpl(quint32 mask) const noexcept;
287 qsizetype numActiveRemoteStreams() const noexcept;
288 qsizetype numActiveLocalStreams() const noexcept;
289
290 bool sendClientPreface();
291 bool sendSETTINGS();
292 bool sendServerPreface();
293 bool serverCheckClientPreface();
294 bool sendWINDOW_UPDATE(quint32 streamID, quint32 delta);
295 bool sendGOAWAY(Http2::Http2Error errorCode);
296 bool sendSETTINGS_ACK();
297
298 void handleDATA();
299 void handleHEADERS();
300 void handlePRIORITY();
301 void handleRST_STREAM();
302 void handleSETTINGS();
303 void handlePUSH_PROMISE();
304 void handlePING();
305 void handleGOAWAY();
306 void handleWINDOW_UPDATE();
307 void handleCONTINUATION();
308
309 void handleContinuedHEADERS();
310
311 bool acceptSetting(Http2::Settings identifier, quint32 newValue);
312
313 bool readClientPreface();
314
315 explicit QHttp2Connection(QIODevice *socket);
316
317 enum class Type { Client, Server } m_connectionType = Type::Client;
318
319 bool waitingForSettingsACK = false;
320
321 static constexpr quint32 maxAcceptableTableSize = 16 * HPack::FieldLookupTable::DefaultSize;
322 // HTTP/2 4.3: Header compression is stateful. One compression context and
323 // one decompression context are used for the entire connection.
324 HPack::Decoder decoder = HPack::Decoder(HPack::FieldLookupTable::DefaultSize);
325 HPack::Encoder encoder = HPack::Encoder(HPack::FieldLookupTable::DefaultSize, true);
326
327 QHttp2Configuration m_config;
328 QHash<quint32, QPointer<QHttp2Stream>> m_streams;
329 QSet<quint32> m_blockedStreams;
330 QHash<QUrl, quint32> m_promisedStreams;
331 QList<quint32> m_resetStreamIDs;
332
333 std::optional<QByteArray> m_lastPingSignature = std::nullopt;
334 quint32 m_nextStreamID = 1;
335
336 // Peer's max frame size (this min is the default value
337 // we start with, that can be updated by SETTINGS frame):
338 quint32 maxFrameSize = Http2::minPayloadLimit;
339
340 Http2::FrameReader frameReader;
341 Http2::Frame inboundFrame;
342 Http2::FrameWriter frameWriter;
343
344 // Temporary storage to assemble HEADERS' block
345 // from several CONTINUATION frames ...
346 bool continuationExpected = false;
347 std::vector<Http2::Frame> continuedFrames;
348
349 // Control flow:
350
351 // This is how many concurrent streams our peer allows us, 100 is the
352 // initial value, can be updated by the server's SETTINGS frame(s):
353 quint32 m_maxConcurrentStreams = Http2::maxConcurrentStreams;
354 // While we allow sending SETTTINGS_MAX_CONCURRENT_STREAMS to limit our peer,
355 // it's just a hint and we do not actually enforce it (and we can continue
356 // sending requests and creating streams while maxConcurrentStreams allows).
357
358 // This is our (client-side) maximum possible receive window size, we set
359 // it in a ctor from QHttp2Configuration, it does not change after that.
360 // The default is 64Kb:
361 qint32 maxSessionReceiveWindowSize = Http2::defaultSessionWindowSize;
362
363 // Our session current receive window size, updated in a ctor from
364 // QHttp2Configuration. Signed integer since it can become negative
365 // (it's still a valid window size).
366 qint32 sessionReceiveWindowSize = Http2::defaultSessionWindowSize;
367 // Our per-stream receive window size, default is 64 Kb, will be updated
368 // from QHttp2Configuration. Again, signed - can become negative.
369 qint32 streamInitialReceiveWindowSize = Http2::defaultSessionWindowSize;
370
371 // These are our peer's receive window sizes, they will be updated by the
372 // peer's SETTINGS and WINDOW_UPDATE frames, defaults presumed to be 64Kb.
373 qint32 sessionSendWindowSize = Http2::defaultSessionWindowSize;
374 qint32 streamInitialSendWindowSize = Http2::defaultSessionWindowSize;
375
376 // Our peer's header size limitations. It's unlimited by default, but can
377 // be changed via peer's SETTINGS frame.
378 quint32 m_maxHeaderListSize = (std::numeric_limits<quint32>::max)();
379 // While we can send SETTINGS_MAX_HEADER_LIST_SIZE value (our limit on
380 // the headers size), we never enforce it, it's just a hint to our peer.
381
382 bool m_upgradedConnection = false;
383 bool m_goingAway = false;
384 bool pushPromiseEnabled = false;
385 quint32 m_lastIncomingStreamID = Http2::connectionStreamID;
386
387 // Server-side only:
388 bool m_waitingForClientPreface = false;
389
390 friend tst_QHttp2Connection;
391};
392
393QT_END_NAMESPACE
394
395#endif // HTTP2CONNECTION_P_H
396

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtbase/src/network/access/qhttp2connection_p.h