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 | |
38 | class tst_QHttp2Connection; |
39 | |
40 | QT_BEGIN_NAMESPACE |
41 | |
42 | template <typename T, typename Err> |
43 | class QH2Expected |
44 | { |
45 | static_assert(!std::is_same_v<T, Err>, "T and Err must be different types"); |
46 | public: |
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 | |
88 | private: |
89 | std::variant<T, Err> m_data; |
90 | }; |
91 | |
92 | class QHttp2Connection; |
93 | class Q_NETWORK_EXPORT QHttp2Stream : public QObject |
94 | { |
95 | Q_OBJECT |
96 | Q_DISABLE_COPY_MOVE(QHttp2Stream) |
97 | |
98 | public: |
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 | |
126 | Q_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 | |
140 | public 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 | |
148 | private Q_SLOTS: |
149 | void maybeResumeUpload(); |
150 | void uploadDeviceReadChannelFinished(); |
151 | void uploadDeviceDestroyed(); |
152 | |
153 | private: |
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 | |
205 | class Q_NETWORK_EXPORT QHttp2Connection : public QObject |
206 | { |
207 | Q_OBJECT |
208 | Q_DISABLE_COPY_MOVE(QHttp2Connection) |
209 | |
210 | public: |
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 | |
255 | Q_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); |
265 | public Q_SLOTS: |
266 | bool sendPing(); |
267 | bool sendPing(QByteArrayView data); |
268 | void handleReadyRead(); |
269 | void handleConnectionClosure(); |
270 | |
271 | private: |
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 | |
393 | QT_END_NAMESPACE |
394 | |
395 | #endif // HTTP2CONNECTION_P_H |
396 |
Definitions
- QH2Expected
- QH2Expected
- QH2Expected
- QH2Expected
- QH2Expected
- operator=
- operator=
- operator=
- operator=
- unwrap
- error
- ok
- has_value
- has_error
- clear
- QHttp2Stream
- QHttp2Stream
- State
- DefaultPriority
- streamID
- isUploadingDATA
- state
- isActive
- isPromisedStream
- wasReset
- wasResetbyPeer
- RST_STREAMCodeReceived
- RST_STREAMCodeSent
- receivedHeaders
- downloadBuffer
- takeDownloadBuffer
- clearDownloadBuffer
- getConnection
- StateTransition
- QHttp2Connection
- QHttp2Connection
- CreateStreamError
- PingState
- promisedStream
- close
- isGoingAway
- maxConcurrentStreams
- maxHeaderListSize
- isUpgradedConnection
- getSocket
- Type
Learn Advanced QML with KDAB
Find out more