Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
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/qvarlengtharray.h>
23#include <QtCore/qxpfunctional.h>
24#include <QtNetwork/qhttp2configuration.h>
25#include <QtNetwork/qtcpsocket.h>
26
27#include <private/http2protocol_p.h>
28#include <private/http2streams_p.h>
29#include <private/http2frames_p.h>
30#include <private/hpack_p.h>
31
32#include <variant>
33#include <optional>
34#include <type_traits>
35#include <limits>
36
38
39template <typename T, typename Err>
41{
42 static_assert(!std::is_same_v<T, Err>, "T and Err must be different types");
43public:
44 // Rule Of Zero applies
45 QH2Expected(T &&value) : m_data(std::move(value)) { }
46 QH2Expected(const T &value) : m_data(value) { }
47 QH2Expected(Err &&error) : m_data(std::move(error)) { }
48 QH2Expected(const Err &error) : m_data(error) { }
49
51 {
52 m_data = std::move(value);
53 return *this;
54 }
56 {
57 m_data = value;
58 return *this;
59 }
61 {
62 m_data = std::move(error);
63 return *this;
64 }
66 {
67 m_data = error;
68 return *this;
69 }
70 T unwrap() const
71 {
72 Q_ASSERT(ok());
73 return std::get<T>(m_data);
74 }
75 Err error() const
76 {
78 return std::get<Err>(m_data);
79 }
80 bool ok() const noexcept { return std::holds_alternative<T>(m_data); }
81 bool has_value() const noexcept { return ok(); }
82 bool has_error() const noexcept { return std::holds_alternative<Err>(m_data); }
83 void clear() noexcept { m_data.reset(); }
84
85private:
86 std::variant<T, Err> m_data;
87};
88
90class Q_NETWORK_EXPORT QHttp2Stream : public QObject
91{
93 Q_DISABLE_COPY_MOVE(QHttp2Stream)
94
95public:
96 enum class State { Idle, ReservedRemote, Open, HalfClosedLocal, HalfClosedRemote, Closed };
98 constexpr static quint8 DefaultPriority = 127;
99
100 ~QHttp2Stream() noexcept;
101
102 // HTTP2 things
103 quint32 streamID() const noexcept { return m_streamID; }
104
105 // Are we waiting for a larger send window before sending more data?
106 bool isUploadBlocked() const noexcept;
107 bool isUploadingDATA() const noexcept { return m_uploadByteDevice != nullptr; }
108 State state() const noexcept { return m_state; }
109 bool isActive() const noexcept { return m_state != State::Closed && m_state != State::Idle; }
110 bool isPromisedStream() const noexcept { return m_isReserved; }
111 bool wasReset() const noexcept { return m_RST_STREAM_code.has_value(); }
112 quint32 RST_STREAM_code() const noexcept { return m_RST_STREAM_code.value_or(0); }
113 // Just the list of headers, as received, may contain duplicates:
114 HPack::HttpHeader receivedHeaders() const noexcept { return m_headers; }
115
116 QByteDataBuffer downloadBuffer() const noexcept { return m_downloadBuffer; }
117
119 void headersReceived(const HPack::HttpHeader &headers, bool endStream);
121 void errorOccurred(quint32 errorCode, const QString &errorString);
125 void dataReceived(const QByteArray &data, bool endStream);
126
128 void uploadDeviceError(const QString &errorString);
130
131public Q_SLOTS:
132 bool sendRST_STREAM(quint32 errorCode);
133 bool sendHEADERS(const HPack::HttpHeader &headers, bool endStream,
134 quint8 priority = DefaultPriority);
135 void sendDATA(QIODevice *device, bool endStream);
136 void sendDATA(QNonContiguousByteDevice *device, bool endStream);
137 void sendWINDOW_UPDATE(quint32 delta);
138
139private Q_SLOTS:
140 void maybeResumeUpload();
141 void uploadDeviceReadChannelFinished();
142 void uploadDeviceDestroyed();
143
144private:
145 friend class QHttp2Connection;
146 QHttp2Stream(QHttp2Connection *connection, quint32 streamID) noexcept;
147
148 [[nodiscard]] QHttp2Connection *getConnection() const
149 {
150 return qobject_cast<QHttp2Connection *>(parent());
151 }
152
153 enum class StateTransition {
154 Open,
155 CloseLocal,
156 CloseRemote,
157 RST,
158 };
159
160 void setState(State newState);
161 void transitionState(StateTransition transition);
162 void internalSendDATA();
163 void finishSendDATA();
164
165 void handleDATA(const Http2::Frame &inboundFrame);
166 void handleHEADERS(Http2::FrameFlags frameFlags, const HPack::HttpHeader &headers);
167 void handleRST_STREAM(const Http2::Frame &inboundFrame);
168 void handleWINDOW_UPDATE(const Http2::Frame &inboundFrame);
169
170 void finishWithError(quint32 errorCode, const QString &message);
171 void finishWithError(quint32 errorCode);
172
173 // Keep it const since it never changes after creation
174 const quint32 m_streamID = 0;
175 qint32 m_recvWindow = 0;
176 qint32 m_sendWindow = 0;
177 bool m_endStreamAfterDATA = false;
178 std::optional<quint32> m_RST_STREAM_code;
179
180 QIODevice *m_uploadDevice = nullptr;
181 QNonContiguousByteDevice *m_uploadByteDevice = nullptr;
182
183 QByteDataBuffer m_downloadBuffer;
184 State m_state = State::Idle;
185 HPack::HttpHeader m_headers;
186 bool m_isReserved = false;
187};
188
189class Q_NETWORK_EXPORT QHttp2Connection : public QObject
190{
192 Q_DISABLE_COPY_MOVE(QHttp2Connection)
193
194public:
195 enum class CreateStreamError {
196 MaxConcurrentStreamsReached,
197 StreamIdsExhausted,
198 ReceivedGOAWAY,
199 };
200 Q_ENUM(CreateStreamError)
201
202 enum class PingState {
203 Ping,
204 PongSignatureIdentical,
205 PongSignatureChanged,
206 PongNoPingSent, // We got an ACKed ping but had not sent any
207 };
208
209 // For a pre-established connection:
210 [[nodiscard]] static QHttp2Connection *
211 createUpgradedConnection(QIODevice *socket, const QHttp2Configuration &config);
212 // For a new connection, potential TLS handshake must already be finished:
213 [[nodiscard]] static QHttp2Connection *createDirectConnection(QIODevice *socket,
215 [[nodiscard]] static QHttp2Connection *
216 createDirectServerConnection(QIODevice *socket, const QHttp2Configuration &config);
218
219 [[nodiscard]] QH2Expected<QHttp2Stream *, CreateStreamError> createStream();
220
221 QHttp2Stream *getStream(quint32 streamId) const;
222 QHttp2Stream *promisedStream(const QUrl &streamKey) const
223 {
224 if (quint32 id = m_promisedStreams.value(streamKey, 0); id)
225 return m_streams.value(id);
226 return nullptr;
227 }
228
229 void close() { sendGOAWAY(Http2::HTTP2_NO_ERROR); }
230
231 bool isGoingAway() const noexcept { return m_goingAway; }
232
233 quint32 maxConcurrentStreams() const noexcept { return m_maxConcurrentStreams; }
234 quint32 maxHeaderListSize() const noexcept { return m_maxHeaderListSize; }
235
236 bool isUpgradedConnection() const noexcept { return m_upgradedConnection; }
237
241 void errorReceived(/*@future: add as needed?*/); // Connection errors only, no stream-specific errors
245 void errorOccurred(Http2::Http2Error errorCode, const QString &errorString);
246 void receivedGOAWAY(quint32 errorCode, quint32 lastStreamID);
247public Q_SLOTS:
248 bool sendPing();
249 bool sendPing(QByteArrayView data);
250 void handleReadyRead();
251 void handleConnectionClosure();
252
253private:
254 friend class QHttp2Stream;
255 [[nodiscard]] QIODevice *getSocket() const { return qobject_cast<QIODevice *>(parent()); }
256
257 QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError> createStreamInternal();
258 QHttp2Stream *createStreamInternal_impl(quint32 streamID);
259
260 bool isInvalidStream(quint32 streamID) noexcept;
261 bool streamWasReset(quint32 streamID) noexcept;
262
263 void connectionError(Http2::Http2Error errorCode,
264 const char *message); // Connection failed to be established?
265 void setH2Configuration(QHttp2Configuration config);
266 void closeSession();
267 qsizetype numActiveStreamsImpl(quint32 mask) const noexcept;
268 qsizetype numActiveRemoteStreams() const noexcept;
269 qsizetype numActiveLocalStreams() const noexcept;
270
271 bool sendClientPreface();
272 bool sendSETTINGS();
273 bool sendServerPreface();
274 bool serverCheckClientPreface();
275 bool sendWINDOW_UPDATE(quint32 streamID, quint32 delta);
276 bool sendGOAWAY(quint32 errorCode);
277 bool sendSETTINGS_ACK();
278
279 void handleDATA();
280 void handleHEADERS();
281 void handlePRIORITY();
282 void handleRST_STREAM();
283 void handleSETTINGS();
284 void handlePUSH_PROMISE();
285 void handlePING();
286 void handleGOAWAY();
287 void handleWINDOW_UPDATE();
288 void handleCONTINUATION();
289
290 void handleContinuedHEADERS();
291
292 bool acceptSetting(Http2::Settings identifier, quint32 newValue);
293
294 bool readClientPreface();
295
297
298 enum class Type { Client, Server } m_connectionType = Type::Client;
299
300 bool waitingForSettingsACK = false;
301
302 static constexpr quint32 maxAcceptableTableSize = 16 * HPack::FieldLookupTable::DefaultSize;
303 // HTTP/2 4.3: Header compression is stateful. One compression context and
304 // one decompression context are used for the entire connection.
307
308 QHttp2Configuration m_config;
309 QHash<quint32, QPointer<QHttp2Stream>> m_streams;
310 QHash<QUrl, quint32> m_promisedStreams;
311 QVarLengthArray<quint32> m_resetStreamIDs;
312 std::optional<QByteArray> m_lastPingSignature = std::nullopt;
313 quint32 m_nextStreamID = 1;
314
315 // Peer's max frame size (this min is the default value
316 // we start with, that can be updated by SETTINGS frame):
317 quint32 maxFrameSize = Http2::minPayloadLimit;
318
319 Http2::FrameReader frameReader;
320 Http2::Frame inboundFrame;
321 Http2::FrameWriter frameWriter;
322
323 // Temporary storage to assemble HEADERS' block
324 // from several CONTINUATION frames ...
325 bool continuationExpected = false;
326 std::vector<Http2::Frame> continuedFrames;
327
328 // Control flow:
329
330 // This is how many concurrent streams our peer allows us, 100 is the
331 // initial value, can be updated by the server's SETTINGS frame(s):
332 quint32 m_maxConcurrentStreams = Http2::maxConcurrentStreams;
333 // While we allow sending SETTTINGS_MAX_CONCURRENT_STREAMS to limit our peer,
334 // it's just a hint and we do not actually enforce it (and we can continue
335 // sending requests and creating streams while maxConcurrentStreams allows).
336
337 // This is our (client-side) maximum possible receive window size, we set
338 // it in a ctor from QHttp2Configuration, it does not change after that.
339 // The default is 64Kb:
341
342 // Our session current receive window size, updated in a ctor from
343 // QHttp2Configuration. Signed integer since it can become negative
344 // (it's still a valid window size).
345 qint32 sessionReceiveWindowSize = Http2::defaultSessionWindowSize;
346 // Our per-stream receive window size, default is 64 Kb, will be updated
347 // from QHttp2Configuration. Again, signed - can become negative.
348 qint32 streamInitialReceiveWindowSize = Http2::defaultSessionWindowSize;
349
350 // These are our peer's receive window sizes, they will be updated by the
351 // peer's SETTINGS and WINDOW_UPDATE frames, defaults presumed to be 64Kb.
352 qint32 sessionSendWindowSize = Http2::defaultSessionWindowSize;
353 qint32 streamInitialSendWindowSize = Http2::defaultSessionWindowSize;
354
355 // Our peer's header size limitations. It's unlimited by default, but can
356 // be changed via peer's SETTINGS frame.
357 quint32 m_maxHeaderListSize = (std::numeric_limits<quint32>::max)();
358 // While we can send SETTINGS_MAX_HEADER_LIST_SIZE value (our limit on
359 // the headers size), we never enforce it, it's just a hint to our peer.
360
361 bool m_upgradedConnection = false;
362 bool m_goingAway = false;
363 bool pushPromiseEnabled = false;
364 quint32 m_lastIncomingStreamID = Http2::connectionStreamID;
365
366 // Server-side only:
367 bool m_waitingForClientPreface = false;
368};
369
371
372#endif // HTTP2CONNECTION_P_H
IOBluetoothDevice * device
\inmodule QtCore
Definition qbytearray.h:57
bool has_error() const noexcept
bool ok() const noexcept
void clear() noexcept
Err error() const
QH2Expected & operator=(T &&value)
QH2Expected(const T &value)
QH2Expected(T &&value)
QH2Expected(Err &&error)
bool has_value() const noexcept
QH2Expected & operator=(Err &&error)
QH2Expected(const Err &error)
QH2Expected & operator=(const T &value)
QH2Expected & operator=(const Err &error)
The QHttp2Configuration class controls HTTP/2 parameters and settings.
\inmodule QtNetwork
void receivedGOAWAY(quint32 errorCode, quint32 lastStreamID)
This signal is emitted when the connection has received a GOAWAY frame.
bool isGoingAway() const noexcept
Returns true if the connection is in the process of being closed, or false otherwise.
void pingFrameRecived(QHttp2Connection::PingState state)
QHttp2Stream * promisedStream(const QUrl &streamKey) const
Returns a pointer to the stream that was promised with the given streamKey, if any.
void errorOccurred(Http2::Http2Error errorCode, const QString &errorString)
This signal is emitted when the connection has encountered an error.
void connectionClosed()
This signal is emitted when the connection has been closed.
void settingsFrameReceived()
This signal is emitted when the connection has received a SETTINGS frame.
quint32 maxConcurrentStreams() const noexcept
Returns the maximum number of concurrent streams we are allowed to have active at any given time.
void close()
This sends a GOAWAY frame on the connection stream, gracefully closing the connection.
bool isUpgradedConnection() const noexcept
Returns true if this connection was created as a result of an HTTP/1 upgrade to HTTP/2,...
void errorReceived()
This signal is emitted when the connection has received an error.
void newIncomingStream(QHttp2Stream *stream)
This signal is emitted when a new stream is received from the remote peer.
void newPromisedStream(QHttp2Stream *stream)
This signal is emitted when the remote peer has promised a new stream.
quint32 maxHeaderListSize() const noexcept
Returns the maximum size of the header which the peer is willing to accept.
\inmodule QtNetwork
bool isActive() const noexcept
Returns true if the stream has been opened and is not yet closed.
void dataReceived(const QByteArray &data, bool endStream)
This signal is emitted when the stream has received a DATA frame from the remote peer.
void stateChanged(QHttp2Stream::State newState)
This signal is emitted when the state of the stream changes.
State state() const noexcept
Returns the current state of the stream.
quint32 RST_STREAM_code() const noexcept
Returns the HTTP/2 error code if the stream was reset by the remote peer.
bool isPromisedStream() const noexcept
Returns true if the stream was promised by the remote peer.
void uploadDeviceError(const QString &errorString)
This signal is emitted if the upload device encounters an error while sending data.
void uploadBlocked()
This signal is emitted when the stream is unable to send more data because the remote peer's receive ...
void bytesWritten(qint64 bytesWritten)
This signal is emitted when the stream has written bytesWritten bytes to the network.
HPack::HttpHeader receivedHeaders() const noexcept
Returns the headers received from the remote peer, if any.
bool wasReset() const noexcept
Returns true if the stream was reset by the remote peer.
void promisedStreamReceived(quint32 newStreamID)
This signal is emitted when the remote peer has promised a new stream with the given newStreamID.
void uploadFinished()
This signal is emitted when the stream has finished sending all the data from the upload device.
void headersUpdated()
This signal may be emitted if a new HEADERS frame was received after already processing a previous HE...
void errorOccurred(quint32 errorCode, const QString &errorString)
This signal is emitted when the stream has encountered an error.
void headersReceived(const HPack::HttpHeader &headers, bool endStream)
This signal is emitted when the remote peer has sent a HEADERS frame, and potentially some CONTINUATI...
QByteDataBuffer downloadBuffer() const noexcept
Returns the buffer containing the data received from the remote peer.
\inmodule QtCore \reentrant
Definition qiodevice.h:34
\inmodule QtCore
Definition qobject.h:103
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
\inmodule QtCore
Definition qurl.h:94
else opt state
[0]
void newState(QList< State > &states, const char *token, const char *lexem, bool pre)
std::vector< HeaderField > HttpHeader
Definition hpack_p.h:33
@ maxConcurrentStreams
@ defaultSessionWindowSize
@ connectionStreamID
@ minPayloadLimit
const qint32 maxSessionReceiveWindowSize((quint32(1)<< 31) - 1)
Combined button and popup list for selecting options.
DBusConnection * connection
EGLStreamKHR stream
EGLConfig config
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
GLenum GLsizei GLuint GLint * bytesWritten
GLenum GLuint id
[7]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint GLsizei const GLchar * message
GLint GLint GLint GLint GLint GLint GLint GLbitfield mask
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define Q_ENUM(x)
#define Q_OBJECT
#define Q_SLOTS
#define Q_SIGNALS
unsigned int quint32
Definition qtypes.h:50
int qint32
Definition qtypes.h:49
ptrdiff_t qsizetype
Definition qtypes.h:165
long long qint64
Definition qtypes.h:60
unsigned char quint8
Definition qtypes.h:46
#define explicit
QTcpSocket * socket
[1]
Definition moc.h:23