Coverage Report

Created: 2026-08-17 07:50

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