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.cpp
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:critical reason:network-protocol
4
7
8#include <private/bitstreams_p.h>
9
10#include <QtCore/private/qnumeric_p.h>
11#include <QtCore/private/qiodevice_p.h>
12#include <QtCore/private/qnoncontiguousbytedevice_p.h>
13#include <QtCore/qcoreapplication.h>
14#include <QtCore/QRandomGenerator>
15#include <QtCore/qloggingcategory.h>
16
17#include <algorithm>
18#include <memory>
19#include <chrono>
20
22
23Q_STATIC_LOGGING_CATEGORY(qHttp2ConnectionLog, "qt.network.http2.connection", QtCriticalMsg)
24
25using namespace Qt::StringLiterals;
26using namespace Http2;
27
28/*!
29 \class QHttp2Stream
30 \inmodule QtNetwork
31 \internal
32
33 The QHttp2Stream class represents a single HTTP/2 stream.
34 Must be created by QHttp2Connection.
35
36 \sa QHttp2Connection
37*/
38
39/*!
40 \struct QHttp2Stream::Configuration
41 \inmodule QtNetwork
42 \internal
43
44 \brief Configuration options for a QHttp2Stream.
45
46 The Configuration struct holds options that control stream behavior.
47
48 \sa QHttp2Connection::createStream()
49*/
50
51/*!
52 \variable QHttp2Stream::Configuration::useDownloadBuffer
53
54 Controls whether incoming DATA frames, from QHttp2Stream::dataReceived(),
55 are buffered. The default is \c true.
56
57 You may disable buffering for client-initiated streams when the
58 application processes DATA immediately.
59
60 Buffering must remain enabled for pushed streams. A pushed stream can
61 receive DATA before the application becomes aware of them and the buffered
62 DATA is required to deliver the pushed response.
63
64 \sa QHttp2Stream::downloadBuffer(), QHttp2Stream::takeDownloadBuffer(),
65 QHttp2Configuration::serverPushEnabled(), QHttp2Stream::dataReceived()
66*/
67
68/*!
69 \variable QHttp2Stream::Configuration::useHeaderBuffer
70
71 Controls whether received headers, from QHttp2Stream::headersReceived(),
72 are accumulated for later retrieval via QHttp2Stream::receivedHeaders().
73 The default is \c true.
74
75 You may disable accumulation for client-initiated streams when the
76 application consumes the headersReceived() signal directly. When disabled,
77 the \l{headersUpdated()} signal is not emitted.
78
79 Buffering must remain enabled for pushed streams. A pushed stream can
80 receive headers before the application becomes aware of it and the buffered
81 headers are required to deliver the pushed response.
82
83 \sa QHttp2Stream::receivedHeaders(), QHttp2Stream::headersReceived(),
84 QHttp2Configuration::serverPushEnabled()
85*/
86
87QHttp2Stream::QHttp2Stream(QHttp2Connection *connection, quint32 streamID,
88 Configuration configuration) noexcept
89 : QObject(connection), m_streamID(streamID), m_configuration(configuration)
90{
91 Q_ASSERT(connection);
92 Q_ASSERT(streamID); // stream id 0 is reserved for connection control messages
93 qCDebug(qHttp2ConnectionLog, "[%p] new stream %u", connection, streamID);
94}
95
96QHttp2Stream::~QHttp2Stream() noexcept {
97 if (auto *connection = getConnection()) {
98 if (m_state != State::Idle && m_state != State::Closed) {
99 qCDebug(qHttp2ConnectionLog, "[%p] stream %u, destroyed while still open", connection,
100 m_streamID);
101 // Check if we can still send data, then send RST_STREAM:
102 if (connection->getSocket()) {
103 if (isUploadingDATA())
104 sendRST_STREAM(CANCEL);
105 else
106 sendRST_STREAM(HTTP2_NO_ERROR);
107 }
108 }
109
110 connection->m_streams.remove(streamID());
111 }
112}
113
114/*!
115 \fn quint32 QHttp2Stream::streamID() const noexcept
116
117 Returns the stream ID of this stream.
118*/
119
120/*!
121 \fn void QHttp2Stream::headersReceived(const HPack::HttpHeader &headers, bool endStream)
122
123 This signal is emitted when the remote peer has sent a HEADERS frame, and
124 potentially some CONTINUATION frames, ending with the END_HEADERS flag
125 to this stream.
126
127 The headers are internally combined and decompressed, and are accessible
128 through the \a headers parameter. If the END_STREAM flag was set, the
129 \a endStream parameter will be \c true, indicating that the peer does not
130 intend to send any more frames on this stream.
131
132 \sa receivedHeaders()
133*/
134
135/*!
136 \fn void QHttp2Stream::headersUpdated()
137
138 This signal may be emitted if a new HEADERS frame was received after
139 already processing a previous HEADERS frame.
140
141 \sa headersReceived(), receivedHeaders()
142*/
143
144/*!
145 \fn void QHttp2Stream::errorOccurred(Http2::Http2Error errorCode, const QString &errorString)
146
147 This signal is emitted when the stream has encountered an error. The
148 \a errorCode parameter is the HTTP/2 error code, and the \a errorString
149 parameter is a human-readable description of the error.
150
151 \sa https://www.rfc-editor.org/rfc/rfc7540#section-7
152*/
153
154/*!
155 \fn void QHttp2Stream::stateChanged(State newState)
156
157 This signal is emitted when the state of the stream changes. The \a newState
158 parameter is the new state of the stream.
159
160 Examples of this is sending or receiving a frame with the END_STREAM flag.
161 This will transition the stream to the HalfClosedLocal or HalfClosedRemote
162 state, respectively.
163
164 \sa state()
165*/
166
167
168/*!
169 \fn void QHttp2Stream::promisedStreamReceived(quint32 newStreamID)
170
171 This signal is emitted when the remote peer has promised a new stream with
172 the given \a newStreamID.
173
174 \sa QHttp2Connection::promisedStream()
175*/
176
177/*!
178 \fn void QHttp2Stream::uploadBlocked()
179
180 This signal is emitted when the stream is unable to send more data because
181 the remote peer's receive window is full.
182
183 This is mostly intended for diagnostics as there is no expectation that the
184 user can do anything to react to this.
185*/
186
187/*!
188 \fn void QHttp2Stream::dataReceived(const QByteArray &data, bool endStream)
189
190 This signal is emitted when the stream has received a DATA frame from the
191 remote peer. The \a data parameter contains the payload of the frame, and
192 the \a endStream parameter is \c true if the END_STREAM flag was set.
193
194 \sa downloadBuffer()
195*/
196
197/*!
198 \fn void QHttp2Stream::bytesWritten(qint64 bytesWritten)
199
200 This signal is emitted when the stream has written \a bytesWritten bytes to
201 the network.
202*/
203
204/*!
205 \fn void QHttp2Stream::uploadDeviceError(const QString &errorString)
206
207 This signal is emitted if the upload device encounters an error while
208 sending data. The \a errorString parameter is a human-readable description
209 of the error.
210*/
211
212/*!
213 \fn void QHttp2Stream::uploadFinished()
214
215 This signal is emitted when the stream has finished sending all the data
216 from the upload device.
217
218 If the END_STREAM flag was set for sendDATA() then the stream will be
219 closed for further writes before this signal is emitted.
220*/
221
222/*!
223 \fn bool QHttp2Stream::isUploadingDATA() const noexcept
224
225 Returns \c true if the stream is currently sending DATA frames.
226*/
227
228/*!
229 \fn State QHttp2Stream::state() const noexcept
230
231 Returns the current state of the stream.
232
233 \sa stateChanged()
234*/
235/*!
236 \fn bool QHttp2Stream::isActive() const noexcept
237
238 Returns \c true if the stream has been opened and is not yet closed.
239*/
240/*!
241 \fn bool QHttp2Stream::isPromisedStream() const noexcept
242
243 Returns \c true if the stream was promised by the remote peer.
244*/
245/*!
246 \fn bool QHttp2Stream::wasReset() const noexcept
247
248 Returns \c true if the stream was reset by the remote peer.
249*/
250/*!
251 \fn quint32 QHttp2Stream::RST_STREAM_code() const noexcept
252
253 Returns the HTTP/2 error code if the stream was reset by the remote peer.
254 If the stream was not reset, this function returns 0.
255*/
256/*!
257 \fn HPack::HttpHeader QHttp2Stream::receivedHeaders() const noexcept
258
259 Returns the headers received from the remote peer, if any.
260*/
261/*!
262 \fn QByteDataBuffer QHttp2Stream::downloadBuffer() const noexcept
263
264 Returns the buffer containing the data received from the remote peer.
265*/
266
267/*!
268 \fn QHttp2Stream::Configuration QHttp2Stream::configuration() const
269
270 Returns the configuration of this stream.
271*/
272
273void QHttp2Stream::finishWithError(Http2::Http2Error errorCode, const QString &message)
274{
275 qCDebug(qHttp2ConnectionLog, "[%p] stream %u finished with error: %ls (error code: %u)",
276 getConnection(), m_streamID, qUtf16Printable(message), errorCode);
277 transitionState(StateTransition::RST);
278 emit errorOccurred(errorCode, message);
279}
280
281void QHttp2Stream::finishWithError(Http2::Http2Error errorCode)
282{
283 QNetworkReply::NetworkError ignored = QNetworkReply::NoError;
284 QString message;
285 qt_error(errorCode, ignored, message);
286 finishWithError(errorCode, message);
287}
288
289void QHttp2Stream::streamError(Http2::Http2Error errorCode, const QString &message)
290{
291 qCDebug(qHttp2ConnectionLog, "[%p] stream %u finished with error: %ls (error code: %u)",
292 getConnection(), m_streamID, qUtf16Printable(message), errorCode);
293
294 sendRST_STREAM(errorCode);
295 emit errorOccurred(errorCode, message);
296}
297
298/*!
299 Sends a RST_STREAM frame with the given \a errorCode.
300 This closes the stream for both sides, any further frames will be dropped.
301
302 Returns \c false if the stream is closed or idle, also if it fails to send
303 the RST_STREAM frame. Otherwise, returns \c true.
304*/
305bool QHttp2Stream::sendRST_STREAM(Http2::Http2Error errorCode)
306{
307 if (m_state == State::Closed || m_state == State::Idle) {
308 qCDebug(qHttp2ConnectionLog, "[%p] could not send RST_STREAM on %s stream %u",
309 getConnection(), QDebug::toBytes(m_state).constData(), m_streamID);
310 return false;
311 }
312 // Never respond to a RST_STREAM with a RST_STREAM or looping might occur.
313 if (m_RST_STREAM_received.has_value())
314 return false;
315
316 getConnection()->registerStreamAsResetLocally(streamID());
317
318 m_RST_STREAM_sent = errorCode;
319 qCDebug(qHttp2ConnectionLog, "[%p] sending RST_STREAM on stream %u, code: %u", getConnection(),
320 m_streamID, errorCode);
321 transitionState(StateTransition::RST);
322
323 QHttp2Connection *connection = getConnection();
324 FrameWriter &frameWriter = connection->frameWriter;
325 frameWriter.start(FrameType::RST_STREAM, FrameFlag::EMPTY, m_streamID);
326 frameWriter.append(quint32(errorCode));
327 return frameWriter.write(*connection->getSocket());
328}
329
330/*!
331 Sends a DATA frame with the bytes obtained from \a payload.
332
333 This function will send as many DATA frames as needed to send all the data
334 from \a payload. If \a endStream is \c true, the END_STREAM flag will be
335 set.
336
337 Returns \c{true} if we were able to \e{start} writing to the socket,
338 false otherwise.
339 Note that even though we started writing, the socket may error out before
340 this function returns. Call state() for the new status.
341*/
342bool QHttp2Stream::sendDATA(const QByteArray &payload, bool endStream)
343{
344 Q_ASSERT(!m_uploadByteDevice);
345 if (m_state != State::Open && m_state != State::HalfClosedRemote)
346 return false;
347
348 auto *byteDevice = QNonContiguousByteDeviceFactory::create(payload);
349 m_owningByteDevice = true;
350 byteDevice->setParent(this);
351 return sendDATA(byteDevice, endStream);
352}
353
354/*!
355 Sends a DATA frame with the bytes obtained from \a device.
356
357 This function will send as many DATA frames as needed to send all the data
358 from \a device. If \a endStream is \c true, the END_STREAM flag will be set.
359
360 \a device must stay alive for the duration of the upload.
361 A way of doing this is to heap-allocate the \a device and parent it to the
362 QHttp2Stream.
363
364 Returns \c{true} if we were able to \e{start} writing to the socket,
365 false otherwise.
366 Note that even though we started writing, the socket may error out before
367 this function returns. Call state() for the new status.
368*/
369bool QHttp2Stream::sendDATA(QIODevice *device, bool endStream)
370{
371 Q_ASSERT(!m_uploadDevice);
372 Q_ASSERT(!m_uploadByteDevice);
373 Q_ASSERT(device);
374 if (m_state != State::Open && m_state != State::HalfClosedRemote) {
375 qCWarning(qHttp2ConnectionLog, "[%p] attempt to sendDATA on closed stream %u, "
376 "of device: %p.",
377 getConnection(), m_streamID, device);
378 return false;
379 }
380
381 qCDebug(qHttp2ConnectionLog, "[%p] starting sendDATA on stream %u, of device: %p",
382 getConnection(), m_streamID, device);
383 auto *byteDevice = QNonContiguousByteDeviceFactory::create(device);
384 m_owningByteDevice = true;
385 byteDevice->setParent(this);
386 m_uploadDevice = device;
387 return sendDATA(byteDevice, endStream);
388}
389
390/*!
391 Sends a DATA frame with the bytes obtained from \a device.
392
393 This function will send as many DATA frames as needed to send all the data
394 from \a device. If \a endStream is \c true, the END_STREAM flag will be set.
395
396 \a device must stay alive for the duration of the upload.
397 A way of doing this is to heap-allocate the \a device and parent it to the
398 QHttp2Stream.
399
400 Returns \c{true} if we were able to \e{start} writing to the socket,
401 false otherwise.
402 Note that even though we started writing, the socket may error out before
403 this function returns. Call state() for the new status.
404*/
405bool QHttp2Stream::sendDATA(QNonContiguousByteDevice *device, bool endStream)
406{
407 Q_ASSERT(!m_uploadByteDevice);
408 Q_ASSERT(device);
409 if (m_state != State::Open && m_state != State::HalfClosedRemote) {
410 qCWarning(qHttp2ConnectionLog, "[%p] attempt to sendDATA on closed stream %u, "
411 "of device: %p.",
412 getConnection(), m_streamID, device);
413 return false;
414 }
415
416 qCDebug(qHttp2ConnectionLog, "[%p] starting sendDATA on stream %u, of device: %p",
417 getConnection(), m_streamID, device);
418 m_uploadByteDevice = device;
419 m_endStreamAfterDATA = endStream;
420 connect(m_uploadByteDevice, &QNonContiguousByteDevice::readyRead, this,
421 &QHttp2Stream::maybeResumeUpload);
422 connect(m_uploadByteDevice, &QObject::destroyed, this, &QHttp2Stream::uploadDeviceDestroyed);
423
424 internalSendDATA();
425 // There is no early-out in internalSendDATA so if we reach this spot we
426 // have at least started to send something, even if it errors out.
427 return true;
428}
429
430void QHttp2Stream::internalSendDATA()
431{
432 Q_ASSERT(m_uploadByteDevice);
433 QHttp2Connection *connection = getConnection();
434 Q_ASSERT(connection->maxFrameSize > frameHeaderSize);
435 QIODevice *socket = connection->getSocket();
436
437 qCDebug(qHttp2ConnectionLog,
438 "[%p] stream %u, about to write to socket, current session window size: %d, stream "
439 "window size: %d, bytes available: %lld",
440 connection, m_streamID, connection->sessionSendWindowSize, m_sendWindow,
441 m_uploadByteDevice->size() - m_uploadByteDevice->pos());
442
443 qint32 remainingWindowSize = std::min<qint32>(connection->sessionSendWindowSize, m_sendWindow);
444 FrameWriter &frameWriter = connection->frameWriter;
445 qint64 totalBytesWritten = 0;
446 const auto deviceCanRead = [this, connection] {
447 // We take advantage of knowing the internals of one of the devices used.
448 // It will request X bytes to move over to the http thread if there's
449 // not enough left, so we give it a large size. It will anyway return
450 // the size it can actually provide.
451 const qint64 requestSize = connection->maxFrameSize * 10ll;
452 qint64 tmp = 0;
453 return m_uploadByteDevice->readPointer(requestSize, tmp) != nullptr && tmp > 0;
454 };
455
456 bool sentEND_STREAM = false;
457 while (remainingWindowSize && deviceCanRead()) {
458 quint32 bytesWritten = 0;
459 qint32 remainingBytesInFrame = qint32(connection->maxFrameSize);
460 frameWriter.start(FrameType::DATA, FrameFlag::EMPTY, streamID());
461
462 while (remainingWindowSize && deviceCanRead() && remainingBytesInFrame) {
463 const qint32 maxToWrite = std::min(remainingWindowSize, remainingBytesInFrame);
464
465 qint64 outBytesAvail = 0;
466 const char *readPointer = m_uploadByteDevice->readPointer(maxToWrite, outBytesAvail);
467 if (!readPointer || outBytesAvail <= 0) {
468 qCDebug(qHttp2ConnectionLog,
469 "[%p] stream %u, cannot write data, device (%p) has %lld bytes available",
470 connection, m_streamID, m_uploadByteDevice, outBytesAvail);
471 break;
472 }
473 const qint32 bytesToWrite = qint32(std::min<qint64>(maxToWrite, outBytesAvail));
474 frameWriter.append(QByteArrayView(readPointer, bytesToWrite));
475 m_uploadByteDevice->advanceReadPointer(bytesToWrite);
476
477 bytesWritten += bytesToWrite;
478
479 m_sendWindow -= bytesToWrite;
480 Q_ASSERT(m_sendWindow >= 0);
481 connection->sessionSendWindowSize -= bytesToWrite;
482 Q_ASSERT(connection->sessionSendWindowSize >= 0);
483 remainingBytesInFrame -= bytesToWrite;
484 Q_ASSERT(remainingBytesInFrame >= 0);
485 remainingWindowSize -= bytesToWrite;
486 Q_ASSERT(remainingWindowSize >= 0);
487 }
488
489 qCDebug(qHttp2ConnectionLog, "[%p] stream %u, writing %u bytes to socket", connection,
490 m_streamID, bytesWritten);
491 if (!deviceCanRead() && m_uploadByteDevice->atEnd() && m_endStreamAfterDATA) {
492 sentEND_STREAM = true;
493 frameWriter.addFlag(FrameFlag::END_STREAM);
494 }
495 if (!frameWriter.write(*socket)) {
496 qCDebug(qHttp2ConnectionLog, "[%p] stream %u, failed to write to socket", connection,
497 m_streamID);
498 return finishWithError(INTERNAL_ERROR, u"failed to write to socket"_s);
499 }
500
501 totalBytesWritten += bytesWritten;
502 }
503
504 qCDebug(qHttp2ConnectionLog,
505 "[%p] stream %u, wrote %lld bytes total, if the device is not exhausted, we'll write "
506 "more later. Remaining window size: %d",
507 connection, m_streamID, totalBytesWritten, remainingWindowSize);
508
509 emit bytesWritten(totalBytesWritten);
510 if (sentEND_STREAM || (!deviceCanRead() && m_uploadByteDevice->atEnd())) {
511 qCDebug(qHttp2ConnectionLog,
512 "[%p] stream %u, exhausted device %p, sent END_STREAM? %d, %ssending end stream "
513 "after DATA",
514 connection, m_streamID, m_uploadByteDevice, sentEND_STREAM,
515 !sentEND_STREAM && m_endStreamAfterDATA ? "" : "not ");
516 if (!sentEND_STREAM && m_endStreamAfterDATA) {
517 // We need to send an empty DATA frame with END_STREAM since we
518 // have exhausted the device, but we haven't sent END_STREAM yet.
519 // This can happen if we got a final readyRead to signify no more
520 // data available, but we hadn't sent the END_STREAM flag yet.
521 frameWriter.start(FrameType::DATA, FrameFlag::END_STREAM, streamID());
522 frameWriter.write(*socket);
523 }
524 finishSendDATA();
525 } else if (isUploadBlocked()) {
526 qCDebug(qHttp2ConnectionLog, "[%p] stream %u, upload blocked", connection, m_streamID);
527 emit uploadBlocked();
528 }
529}
530
531void QHttp2Stream::finishSendDATA()
532{
533 if (m_endStreamAfterDATA)
534 transitionState(StateTransition::CloseLocal);
535
536 disconnect(m_uploadByteDevice, nullptr, this, nullptr);
537 m_uploadDevice = nullptr;
538 if (m_owningByteDevice) {
539 m_owningByteDevice = false;
540 delete m_uploadByteDevice;
541 }
542 m_uploadByteDevice = nullptr;
543 emit uploadFinished();
544}
545
546void QHttp2Stream::maybeResumeUpload()
547{
548 qCDebug(qHttp2ConnectionLog,
549 "[%p] stream %u, maybeResumeUpload. Upload device: %p, bytes available: %lld, blocked? "
550 "%d",
551 getConnection(), m_streamID, m_uploadByteDevice,
552 !m_uploadByteDevice ? 0 : m_uploadByteDevice->size() - m_uploadByteDevice->pos(),
553 isUploadBlocked());
554 if (isUploadingDATA() && !isUploadBlocked())
555 internalSendDATA();
556 else
557 getConnection()->m_blockedStreams.insert(streamID());
558}
559
560/*!
561 Returns \c true if the stream is currently unable to send more data because
562 the remote peer's receive window is full.
563*/
564bool QHttp2Stream::isUploadBlocked() const noexcept
565{
566 constexpr auto MinFrameSize = Http2::frameHeaderSize + 1; // 1 byte payload
567 return isUploadingDATA()
568 && (m_sendWindow <= MinFrameSize
569 || getConnection()->sessionSendWindowSize <= MinFrameSize);
570}
571
572void QHttp2Stream::uploadDeviceReadChannelFinished()
573{
574 maybeResumeUpload();
575}
576
577/*!
578 Sends a HEADERS frame with the given \a headers and \a priority.
579 If \a endStream is \c true, the END_STREAM flag will be set, and the stream
580 will be closed for future writes.
581 If the headers are too large, or the stream is not in the correct state,
582 this function will return \c false. Otherwise, it will return \c true.
583*/
584bool QHttp2Stream::sendHEADERS(const HPack::HttpHeader &headers, bool endStream, quint8 priority)
585{
586 using namespace HPack;
587 if (auto hs = header_size(headers);
588 !hs.first || hs.second > getConnection()->maxHeaderListSize()) {
589 return false;
590 }
591
592 transitionState(StateTransition::Open);
593
594 Q_ASSERT(m_state == State::Open || m_state == State::HalfClosedRemote);
595
596 QHttp2Connection *connection = getConnection();
597
598 qCDebug(qHttp2ConnectionLog, "[%p] stream %u, sending HEADERS frame with %u entries",
599 connection, streamID(), uint(headers.size()));
600
601 QIODevice *socket = connection->getSocket();
602 FrameWriter &frameWriter = connection->frameWriter;
603
604 frameWriter.start(FrameType::HEADERS, FrameFlag::PRIORITY | FrameFlag::END_HEADERS, streamID());
605 if (endStream)
606 frameWriter.addFlag(FrameFlag::END_STREAM);
607
608 frameWriter.append(quint32()); // No stream dependency in Qt.
609 frameWriter.append(priority);
610
611 // Compress in-place:
612 BitOStream outputStream(frameWriter.outboundFrame().buffer);
613
614 // Possibly perform and notify of dynamic table size update:
615 for (auto &maybePendingTableSizeUpdate : connection->pendingTableSizeUpdates) {
616 if (!maybePendingTableSizeUpdate)
617 break; // They are ordered, so if the first one is null, the other one is too.
618 qCDebug(qHttp2ConnectionLog, "[%p] stream %u, sending dynamic table size update of size %u",
619 connection, streamID(), *maybePendingTableSizeUpdate);
620 connection->encoder.setMaxDynamicTableSize(*maybePendingTableSizeUpdate);
621 connection->encoder.encodeSizeUpdate(outputStream, *maybePendingTableSizeUpdate);
622 maybePendingTableSizeUpdate.reset();
623 }
624
625 if (connection->m_connectionType == QHttp2Connection::Type::Client) {
626 if (!connection->encoder.encodeRequest(outputStream, headers))
627 return false;
628 } else {
629 if (!connection->encoder.encodeResponse(outputStream, headers))
630 return false;
631 }
632
633 bool result = frameWriter.writeHEADERS(*socket, connection->maxFrameSize);
634 if (endStream)
635 transitionState(StateTransition::CloseLocal);
636
637 return result;
638}
639
640/*!
641 Sends a WINDOW_UPDATE frame with the given \a delta.
642 This increases our receive window size for this stream, allowing the remote
643 peer to send more data.
644*/
645void QHttp2Stream::sendWINDOW_UPDATE(quint32 delta)
646{
647 QHttp2Connection *connection = getConnection();
648 m_recvWindow += qint32(delta);
649 connection->sendWINDOW_UPDATE(streamID(), delta);
650}
651
652void QHttp2Stream::uploadDeviceDestroyed()
653{
654 if (isUploadingDATA()) {
655 // We're in the middle of sending DATA frames, we need to abort
656 // the stream.
657 const QString message = u"Upload device destroyed while uploading"_s;
658 streamError(CANCEL, message);
659 emit uploadDeviceError(message);
660 }
661 m_uploadDevice = nullptr;
662 m_owningByteDevice = false;
663 m_uploadByteDevice = nullptr;
664}
665
666void QHttp2Stream::setState(State newState)
667{
668 if (m_state == newState)
669 return;
670 qCDebug(qHttp2ConnectionLog, "[%p] stream %u, state changed from %d to %d", getConnection(),
671 streamID(), int(m_state), int(newState));
672 m_state = newState;
673 emit stateChanged(newState);
674 if (m_state == State::Closed)
675 getConnection()->maybeCloseOnGoingAway();
676}
677
678// Changes the state as appropriate given the current state and the transition.
679// Always call this before emitting any signals since the recipient might rely
680// on the new state!
681void QHttp2Stream::transitionState(StateTransition transition)
682{
683 switch (m_state) {
684 case State::Idle:
685 if (transition == StateTransition::Open)
686 setState(State::Open);
687 else
688 Q_UNREACHABLE(); // We should transition to Open before ever getting here
689 break;
690 case State::Open:
691 switch (transition) {
692 case StateTransition::CloseLocal:
693 setState(State::HalfClosedLocal);
694 break;
695 case StateTransition::CloseRemote:
696 setState(State::HalfClosedRemote);
697 break;
698 case StateTransition::RST:
699 setState(State::Closed);
700 break;
701 case StateTransition::Open: // no-op
702 break;
703 }
704 break;
705 case State::HalfClosedLocal:
706 if (transition == StateTransition::CloseRemote || transition == StateTransition::RST)
707 setState(State::Closed);
708 break;
709 case State::HalfClosedRemote:
710 if (transition == StateTransition::CloseLocal || transition == StateTransition::RST)
711 setState(State::Closed);
712 break;
713 case State::ReservedRemote:
714 if (transition == StateTransition::RST) {
715 setState(State::Closed);
716 } else if (transition == StateTransition::CloseLocal) { // Receiving HEADER closes local
717 setState(State::HalfClosedLocal);
718 }
719 break;
720 case State::Closed:
721 break;
722 }
723}
724
725void QHttp2Stream::handleDATA(const Frame &inboundFrame)
726{
727 QHttp2Connection *connection = getConnection();
728
729 qCDebug(qHttp2ConnectionLog,
730 "[%p] stream %u, received DATA frame with payload of %u bytes, closing stream? %s",
731 connection, m_streamID, inboundFrame.payloadSize(),
732 inboundFrame.flags().testFlag(Http2::FrameFlag::END_STREAM) ? "yes" : "no");
733
734 // RFC 9113, 6.1: If a DATA frame is received whose stream is not in the "open" or "half-closed
735 // (local)" state, the recipient MUST respond with a stream error (Section 5.4.2) of type
736 // STREAM_CLOSED;
737 // checked in QHttp2Connection
738 Q_ASSERT(state() != State::HalfClosedRemote && state() != State::Closed);
739
740 if (qint32(inboundFrame.payloadSize()) > m_recvWindow) {
741 qCDebug(qHttp2ConnectionLog,
742 "[%p] stream %u, received DATA frame with payload size %u, "
743 "but recvWindow is %d, sending FLOW_CONTROL_ERROR",
744 connection, m_streamID, inboundFrame.payloadSize(), m_recvWindow);
745 return streamError(FLOW_CONTROL_ERROR, u"data bigger than window size"_s);
746 }
747 // RFC 9113, 6.1: The total number of padding octets is determined by the value of the Pad
748 // Length field. If the length of the padding is the length of the frame payload or greater,
749 // the recipient MUST treat this as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
750 // checked in Framereader
751 Q_ASSERT(inboundFrame.buffer.size() >= frameHeaderSize);
752 Q_ASSERT(inboundFrame.payloadSize() + frameHeaderSize == inboundFrame.buffer.size());
753
754 m_recvWindow -= qint32(inboundFrame.payloadSize());
755 const bool endStream = inboundFrame.flags().testFlag(FrameFlag::END_STREAM);
756 const bool ignoreData = connection->streamIsIgnored(m_streamID);
757 // Uncompress data if needed and append it ...
758 if ((inboundFrame.dataSize() > 0 || endStream) && !ignoreData) {
759 QByteArray fragment(reinterpret_cast<const char *>(inboundFrame.dataBegin()),
760 inboundFrame.dataSize());
761 if (endStream)
762 transitionState(StateTransition::CloseRemote);
763 const auto shouldBuffer = m_configuration.useDownloadBuffer && !fragment.isEmpty();
764 if (shouldBuffer) {
765 // Only non-empty fragments get appended!
766 m_downloadBuffer.append(std::move(fragment));
767 emit dataReceived(m_downloadBuffer.last(), endStream);
768 } else {
769 emit dataReceived(fragment, endStream);
770 }
771 }
772
773 if (!endStream && m_recvWindow < connection->streamInitialReceiveWindowSize / 2) {
774 // @future[consider]: emit signal instead
775 sendWINDOW_UPDATE(quint32(connection->streamInitialReceiveWindowSize - m_recvWindow));
776 }
777}
778
779void QHttp2Stream::handleHEADERS(Http2::FrameFlags frameFlags, const HPack::HttpHeader &headers)
780{
781 if (m_state == State::Idle)
782 transitionState(StateTransition::Open);
783 const bool endStream = frameFlags.testFlag(FrameFlag::END_STREAM);
784 if (endStream)
785 transitionState(StateTransition::CloseRemote);
786 if (!headers.empty() && m_configuration.useHeaderBuffer) {
787 m_headers.insert(m_headers.end(), headers.begin(), headers.end());
788 emit headersUpdated();
789 }
790 emit headersReceived(headers, endStream);
791}
792
793void QHttp2Stream::handleRST_STREAM(const Frame &inboundFrame)
794{
795 if (m_state == State::Closed) // The stream is already closed, we're not sending anything anyway
796 return;
797
798 transitionState(StateTransition::RST);
799 m_RST_STREAM_received = qFromBigEndian<quint32>(inboundFrame.dataBegin());
800 if (isUploadingDATA()) {
801 disconnect(m_uploadByteDevice, nullptr, this, nullptr);
802 m_uploadDevice = nullptr;
803 m_uploadByteDevice = nullptr;
804 }
805 finishWithError(Http2Error(*m_RST_STREAM_received));
806}
807
808void QHttp2Stream::handleWINDOW_UPDATE(const Frame &inboundFrame)
809{
810 const quint32 delta = qFromBigEndian<quint32>(inboundFrame.dataBegin());
811 const bool valid = delta && delta <= quint32(std::numeric_limits<qint32>::max());
812 if (!valid) {
813 // RFC 9113, 6.9.1: a flow-control window increment of 0 is a stream error of
814 // type PROTOCOL_ERROR.
815 qCDebug(qHttp2ConnectionLog,
816 "[%p] stream %u, received WINDOW_UPDATE frame with invalid delta %u, sending "
817 "PROTOCOL_ERROR",
818 getConnection(), m_streamID, delta);
819 return streamError(PROTOCOL_ERROR, u"invalid WINDOW_UPDATE delta"_s);
820 }
821 qint32 sum = 0;
822 if (qAddOverflow(m_sendWindow, qint32(delta), &sum)) {
823 // RFC 9113, 6.9.1: a WINDOW_UPDATE that pushes the window past 2^31-1 is a stream
824 // error of type FLOW_CONTROL_ERROR (the sender sends RST_STREAM).
825 qCDebug(qHttp2ConnectionLog,
826 "[%p] stream %u, WINDOW_UPDATE delta %u overflows the flow-control window, "
827 "sending FLOW_CONTROL_ERROR",
828 getConnection(), m_streamID, delta);
829 return streamError(FLOW_CONTROL_ERROR, u"WINDOW_UPDATE exceeds maximum window"_s);
830 }
831 m_sendWindow = sum;
832 // Stream may have been unblocked, so maybe try to write again
833 if (isUploadingDATA())
834 maybeResumeUpload();
835}
836
837/*!
838 \class QHttp2Connection
839 \inmodule QtNetwork
840 \internal
841
842 The QHttp2Connection class represents a HTTP/2 connection.
843 It can only be created through the static functions
844 createDirectConnection(), createUpgradedConnection(),
845 and createDirectServerConnection().
846
847 createDirectServerConnection() is used for server-side connections, and has
848 certain limitations that a client does not.
849
850 As a client you can create a QHttp2Stream with createStream().
851
852 \sa QHttp2Stream
853*/
854
855/*!
856 \fn void QHttp2Connection::newIncomingStream(QHttp2Stream *stream)
857
858 This signal is emitted when a new \a stream is received from the remote
859 peer.
860*/
861
862/*!
863 \fn void QHttp2Connection::newPromisedStream(QHttp2Stream *stream)
864
865 This signal is emitted when the remote peer has promised a new \a stream.
866*/
867
868/*!
869 \fn void QHttp2Connection::errorReceived()
870
871 This signal is emitted when the connection has received an error.
872*/
873
874/*!
875 \fn void QHttp2Connection::connectionClosed()
876
877 This signal is emitted when the connection has been closed.
878*/
879
880/*!
881 \fn void QHttp2Connection::settingsFrameReceived()
882
883 This signal is emitted when the connection has received a SETTINGS frame.
884*/
885
886/*!
887 \fn void QHttp2Connection::errorOccurred(Http2::Http2Error errorCode, const QString &errorString)
888
889 This signal is emitted when the connection has encountered an error. The
890 \a errorCode parameter is the HTTP/2 error code, and the \a errorString
891 parameter is a human-readable description of the error.
892*/
893
894/*!
895 \fn void QHttp2Connection::receivedGOAWAY(Http2::Http2Error errorCode, quint32 lastStreamID)
896
897 This signal is emitted when the connection has received a GOAWAY frame. The
898 \a errorCode parameter is the HTTP/2 error code, and the \a lastStreamID
899 parameter is the last stream ID that the remote peer will process.
900
901 Any streams of a higher stream ID created by us will be ignored or reset.
902*/
903
904/*!
905 Create a new HTTP2 connection given a \a config and a \a socket.
906 This function assumes that the Upgrade headers etc. in http/1 have already
907 been sent and that the connection is already upgraded to http/2.
908
909 The object returned will be a child to the \a socket, or null on failure.
910*/
911QHttp2Connection *QHttp2Connection::createUpgradedConnection(QIODevice *socket,
912 const QHttp2Configuration &config)
913{
914 Q_ASSERT(socket);
915
916 auto connection = std::unique_ptr<QHttp2Connection>(new QHttp2Connection(socket));
917 connection->setH2Configuration(config);
918 connection->m_connectionType = QHttp2Connection::Type::Client;
919 connection->m_upgradedConnection = true;
920 // HTTP2 connection is already established and request was sent, so stream 1
921 // is already 'active' and is closed for any further outgoing data.
922 QHttp2Stream *stream = connection->createLocalStreamInternal().unwrap();
923 Q_ASSERT(stream->streamID() == 1);
924 stream->setState(QHttp2Stream::State::HalfClosedLocal);
925
926 if (!connection->m_prefaceSent) // Preface is sent as part of initial stream-creation.
927 return nullptr;
928
929 return connection.release();
930}
931
932/*!
933 Create a new HTTP2 connection given a \a config and a \a socket.
934 This function will immediately send the client preface.
935
936 The object returned will be a child to the \a socket, or null on failure.
937*/
938QHttp2Connection *QHttp2Connection::createDirectConnection(QIODevice *socket,
939 const QHttp2Configuration &config)
940{
941 auto connection = std::unique_ptr<QHttp2Connection>(new QHttp2Connection(socket));
942 connection->setH2Configuration(config);
943 connection->m_connectionType = QHttp2Connection::Type::Client;
944
945 return connection.release();
946}
947
948/*!
949 Create a new HTTP2 connection given a \a config and a \a socket.
950
951 The object returned will be a child to the \a socket, or null on failure.
952*/
953QHttp2Connection *QHttp2Connection::createDirectServerConnection(QIODevice *socket,
954 const QHttp2Configuration &config)
955{
956 auto connection = std::unique_ptr<QHttp2Connection>(new QHttp2Connection(socket));
957 connection->setH2Configuration(config);
958 connection->m_connectionType = QHttp2Connection::Type::Server;
959
960 connection->m_nextStreamID = 2; // server-initiated streams must be even
961
962 connection->m_waitingForClientPreface = true;
963
964 return connection.release();
965}
966
967/*!
968 \fn QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError> QHttp2Connection::createStream()
969
970 Creates a stream on this connection, using the default QHttp2Stream::Configuration.
971
972//! [createStream]
973 Automatically picks the next available stream ID and returns a pointer to
974 the new stream, if possible. Otherwise returns an error.
975
976 \sa QHttp2Connection::CreateStreamError, QHttp2Stream
977//! [createStream]
978 \sa createStream(QHttp2Stream::Configuration)
979*/
980
981/*!
982 Creates a stream with \a configuration on this connection.
983
984 \include qhttp2connection.cpp createStream
985*/
986QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError>
987QHttp2Connection::createStream(QHttp2Stream::Configuration configuration)
988{
989 Q_ASSERT(m_connectionType == Type::Client); // This overload is just for clients
990 if (m_nextStreamID > lastValidStreamID)
991 return { QHttp2Connection::CreateStreamError::StreamIdsExhausted };
992 return createLocalStreamInternal(configuration);
993}
994
995QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError>
996QHttp2Connection::createLocalStreamInternal(QHttp2Stream::Configuration conf)
997{
998 if (m_goingAway)
999 return { QHttp2Connection::CreateStreamError::ReceivedGOAWAY };
1000 const quint32 streamID = m_nextStreamID;
1001 if (size_t(m_peerMaxConcurrentStreams) <= size_t(numActiveLocalStreams()))
1002 return { QHttp2Connection::CreateStreamError::MaxConcurrentStreamsReached };
1003
1004 if (QHttp2Stream *ptr = createStreamInternal_impl(streamID, conf)) {
1005 m_nextStreamID += 2;
1006 return {ptr};
1007 }
1008 // Connection could be broken, we could've ran out of memory, we don't know
1009 return { QHttp2Connection::CreateStreamError::UnknownError };
1010}
1011
1012QHttp2Stream *QHttp2Connection::createStreamInternal_impl(quint32 streamID,
1013 QHttp2Stream::Configuration conf)
1014{
1015 Q_ASSERT(streamID > m_lastIncomingStreamID || streamID >= m_nextStreamID);
1016
1017 if (m_connectionType == Type::Client && !m_prefaceSent && !sendClientPreface()) {
1018 qCWarning(qHttp2ConnectionLog, "[%p] Failed to send client preface", this);
1019 return nullptr;
1020 }
1021
1022 auto result = m_streams.tryEmplace(streamID, nullptr);
1023 if (!result.inserted)
1024 return nullptr;
1025 QPointer<QHttp2Stream> &stream = result.iterator.value();
1026 stream = new QHttp2Stream(this, streamID, conf);
1027 stream->m_recvWindow = streamInitialReceiveWindowSize;
1028 stream->m_sendWindow = streamInitialSendWindowSize;
1029
1030 connect(stream, &QHttp2Stream::uploadBlocked, this, [this, stream] {
1031 m_blockedStreams.insert(stream->streamID());
1032 });
1033 *result.iterator = stream;
1034 return *result.iterator;
1035}
1036
1037qsizetype QHttp2Connection::numActiveStreamsImpl(quint32 mask) const noexcept
1038{
1039 const auto shouldCount = [mask](const QPointer<QHttp2Stream> &stream) -> bool {
1040 return stream && (stream->streamID() & 1) == mask && stream->isActive();
1041 };
1042 return std::count_if(m_streams.cbegin(), m_streams.cend(), shouldCount);
1043}
1044
1045/*!
1046 \internal
1047 The number of streams the remote peer has started that are still active.
1048*/
1049qsizetype QHttp2Connection::numActiveRemoteStreams() const noexcept
1050{
1051 const quint32 RemoteMask = m_connectionType == Type::Client ? 0 : 1;
1052 return numActiveStreamsImpl(RemoteMask);
1053}
1054
1055/*!
1056 \internal
1057 The number of streams we have started that are still active.
1058*/
1059qsizetype QHttp2Connection::numActiveLocalStreams() const noexcept
1060{
1061 const quint32 LocalMask = m_connectionType == Type::Client ? 1 : 0;
1062 return numActiveStreamsImpl(LocalMask);
1063}
1064
1065/*!
1066 Return a pointer to a stream with the given \a streamID, or null if no such
1067 stream exists or it was deleted.
1068*/
1069QHttp2Stream *QHttp2Connection::getStream(quint32 streamID) const
1070{
1071 return m_streams.value(streamID, nullptr).get();
1072}
1073
1074/*!
1075 Initiates connection shutdown. When \a errorCode is \c{NO_ERROR}, graceful
1076 shutdown is initiated, allowing existing streams to complete. Otherwise the
1077 connection is closed immediately with an error.
1078*/
1079void QHttp2Connection::close(Http2::Http2Error errorCode)
1080{
1081 if (m_connectionAborted)
1082 return;
1083
1084 if (errorCode == Http2::HTTP2_NO_ERROR) {
1085 if (m_connectionType == Type::Server)
1086 sendInitialServerGracefulShutdownGoaway();
1087 else
1088 sendClientGracefulShutdownGoaway();
1089 } else {
1090 // RFC 9113, 5.4.1: After sending the GOAWAY frame for an error
1091 // condition, the endpoint MUST close the TCP connection
1092 connectionError(errorCode, u"Connection closed with error"_s, false);
1093 }
1094}
1095
1096/*!
1097 \fn QHttp2Stream *QHttp2Connection::promisedStream(const QUrl &streamKey) const
1098
1099 Returns a pointer to the stream that was promised with the given
1100 \a streamKey, if any. Otherwise, returns null.
1101*/
1102
1103/*!
1104 \fn bool QHttp2Connection::isGoingAway() const noexcept
1105
1106 Returns \c true if the connection is in the process of being closed, or
1107 \c false otherwise.
1108*/
1109
1110/*!
1111 \fn quint32 QHttp2Connection::maxConcurrentStreams() const noexcept
1112
1113 Returns the maximum number of concurrent streams we are allowed to have
1114 active at any given time. This is a directional setting, and the remote
1115 peer may have a different value.
1116*/
1117
1118/*!
1119 \fn quint32 QHttp2Connection::maxHeaderListSize() const noexcept
1120
1121 Returns the maximum size of the header which the peer is willing to accept.
1122*/
1123
1124/*!
1125 \fn bool QHttp2Connection::isUpgradedConnection() const noexcept
1126
1127 Returns \c true if this connection was created as a result of an HTTP/1
1128 upgrade to HTTP/2, or \c false otherwise.
1129*/
1130
1131QHttp2Connection::QHttp2Connection(QIODevice *socket) : QObject(socket)
1132{
1133 Q_ASSERT(socket);
1134 Q_ASSERT(socket->isOpen());
1135 Q_ASSERT(socket->openMode() & QIODevice::ReadWrite);
1136 // We don't make any connections directly because this is used in
1137 // in the http2 protocol handler, which is used by
1138 // QHttpNetworkConnectionChannel. Which in turn owns and deals with all the
1139 // socket connections.
1140}
1141
1142QHttp2Connection::~QHttp2Connection()
1143{
1144 // delete streams now so that any calls it might make back to this
1145 // Connection will operate on a valid object.
1146 for (QPointer<QHttp2Stream> &stream : std::exchange(m_streams, {}))
1147 delete stream.get();
1148}
1149
1150bool QHttp2Connection::serverCheckClientPreface()
1151{
1152 if (!m_waitingForClientPreface)
1153 return true;
1154 auto *socket = getSocket();
1155 if (socket->bytesAvailable() < Http2::clientPrefaceLength)
1156 return false;
1157 if (!readClientPreface()) {
1158 socket->close();
1159 emit errorOccurred(Http2Error::PROTOCOL_ERROR, u"invalid client preface"_s);
1160 qCDebug(qHttp2ConnectionLog, "[%p] Invalid client preface", this);
1161 return false;
1162 }
1163 qCDebug(qHttp2ConnectionLog, "[%p] Peer sent valid client preface", this);
1164 m_waitingForClientPreface = false;
1165 if (!sendServerPreface()) {
1166 connectionError(INTERNAL_ERROR, u"Failed to send server preface"_s);
1167 return false;
1168 }
1169 return true;
1170}
1171
1172bool QHttp2Connection::sendPing()
1173{
1174 std::array<char, 8> data;
1175
1176 QRandomGenerator gen;
1177 gen.generate(data.begin(), data.end());
1178 return sendPing(data);
1179}
1180
1181bool QHttp2Connection::sendPing(QByteArrayView data)
1182{
1183 frameWriter.start(FrameType::PING, FrameFlag::EMPTY, connectionStreamID);
1184
1185 Q_ASSERT(data.length() == 8);
1186 if (!m_lastPingSignature) {
1187 m_lastPingSignature = data.toByteArray();
1188 } else {
1189 qCWarning(qHttp2ConnectionLog, "[%p] No PING is sent while waiting for the previous PING.", this);
1190 return false;
1191 }
1192
1193 frameWriter.append((uchar*)data.data(), (uchar*)data.end());
1194 frameWriter.write(*getSocket());
1195 return true;
1196}
1197
1198/*!
1199 This function must be called when you have received a readyRead signal
1200 (or equivalent) from the QIODevice. It will read and process any incoming
1201 HTTP/2 frames and emit signals as appropriate.
1202*/
1203void QHttp2Connection::handleReadyRead()
1204{
1205 /* event loop */
1206 if (m_connectionType == Type::Server && !serverCheckClientPreface())
1207 return;
1208
1209 QIODevice *socket = getSocket();
1210
1211 qCDebug(qHttp2ConnectionLog, "[%p] Receiving data, %lld bytes available", this,
1212 socket->bytesAvailable());
1213
1214 using namespace Http2;
1215 if (!m_prefaceSent)
1216 return;
1217
1218 while (!m_connectionAborted) {
1219 const auto result = frameReader.read(*socket);
1220 if (result != FrameStatus::goodFrame)
1221 qCDebug(qHttp2ConnectionLog, "[%p] Tried to read frame, got %d", this, int(result));
1222 switch (result) {
1223 case FrameStatus::incompleteFrame:
1224 return; // No more complete frames available
1225 case FrameStatus::protocolError:
1226 return connectionError(PROTOCOL_ERROR, u"invalid frame"_s);
1227 case FrameStatus::sizeError: {
1228 const auto streamID = frameReader.inboundFrame().streamID();
1229 const auto frameType = frameReader.inboundFrame().type();
1230 auto stream = getStream(streamID);
1231 // RFC 9113, 4.2: A frame size error in a frame that could alter the state of the
1232 // entire connection MUST be treated as a connection error (Section 5.4.1); this
1233 // includes any frame carrying a field block (Section 4.3) (that is, HEADERS,
1234 // PUSH_PROMISE, and CONTINUATION), a SETTINGS frame, and any frame with a stream
1235 // identifier of 0.
1236 if (frameType == FrameType::HEADERS ||
1237 frameType == FrameType::SETTINGS ||
1238 frameType == FrameType::PUSH_PROMISE ||
1239 frameType == FrameType::CONTINUATION ||
1240 // never reply RST_STREAM with RST_STREAM
1241 frameType == FrameType::RST_STREAM ||
1242 streamID == connectionStreamID)
1243 return connectionError(FRAME_SIZE_ERROR, u"invalid frame size"_s);
1244 // DATA; PRIORITY; WINDOW_UPDATE
1245 if (stream)
1246 return stream->streamError(Http2Error::FRAME_SIZE_ERROR, u"invalid frame size"_s);
1247 else
1248 return; // most likely a closed and deleted stream. Can be ignored.
1249 }
1250 default:
1251 break;
1252 }
1253
1254 Q_ASSERT(result == FrameStatus::goodFrame);
1255
1256 inboundFrame = std::move(frameReader.inboundFrame());
1257
1258 const auto frameType = inboundFrame.type();
1259 qCDebug(qHttp2ConnectionLog, "[%p] Successfully read a frame, with type: %d", this,
1260 int(frameType));
1261
1262 // RFC 9113, 6.2/6.6: A HEADERS/PUSH_PROMISE frame without the END_HEADERS flag set MUST be
1263 // followed by a CONTINUATION frame for the same stream. A receiver MUST treat the
1264 // receipt of any other type of frame or a frame on a different stream as a
1265 // connection error
1266 if (continuationExpected && frameType != FrameType::CONTINUATION)
1267 return connectionError(PROTOCOL_ERROR, u"CONTINUATION expected"_s);
1268
1269 switch (frameType) {
1270 case FrameType::DATA:
1271 handleDATA();
1272 break;
1273 case FrameType::HEADERS:
1274 handleHEADERS();
1275 break;
1276 case FrameType::PRIORITY:
1277 handlePRIORITY();
1278 break;
1279 case FrameType::RST_STREAM:
1280 handleRST_STREAM();
1281 break;
1282 case FrameType::SETTINGS:
1283 handleSETTINGS();
1284 break;
1285 case FrameType::PUSH_PROMISE:
1286 handlePUSH_PROMISE();
1287 break;
1288 case FrameType::PING:
1289 handlePING();
1290 break;
1291 case FrameType::GOAWAY:
1292 handleGOAWAY();
1293 break;
1294 case FrameType::WINDOW_UPDATE:
1295 handleWINDOW_UPDATE();
1296 break;
1297 case FrameType::CONTINUATION:
1298 handleCONTINUATION();
1299 break;
1300 case FrameType::LAST_FRAME_TYPE:
1301 // 5.1 - ignore unknown frames.
1302 break;
1303 }
1304 }
1305}
1306
1307bool QHttp2Connection::readClientPreface()
1308{
1309 auto *socket = getSocket();
1310 Q_ASSERT(socket->bytesAvailable() >= Http2::clientPrefaceLength);
1311 char buffer[Http2::clientPrefaceLength];
1312 const qint64 read = socket->read(buffer, Http2::clientPrefaceLength);
1313 if (read != Http2::clientPrefaceLength)
1314 return false;
1315 return memcmp(buffer, Http2::Http2clientPreface, Http2::clientPrefaceLength) == 0;
1316}
1317
1318/*!
1319 This function must be called when the socket has been disconnected, and will
1320 end all remaining streams with an error.
1321*/
1322void QHttp2Connection::handleConnectionClosure()
1323{
1324 const auto errorString = QCoreApplication::translate("QHttp", "Connection closed");
1325 for (auto it = m_streams.cbegin(), end = m_streams.cend(); it != end; ++it) {
1326 const QPointer<QHttp2Stream> &stream = it.value();
1327 if (stream && stream->isActive())
1328 stream->finishWithError(PROTOCOL_ERROR, errorString);
1329 }
1330}
1331
1332void QHttp2Connection::setH2Configuration(QHttp2Configuration config)
1333{
1334 m_config = std::move(config);
1335
1336 // These values comes from our own API so trust it to be sane.
1337 maxSessionReceiveWindowSize = qint32(m_config.sessionReceiveWindowSize());
1338 pushPromiseEnabled = m_config.serverPushEnabled();
1339 streamInitialReceiveWindowSize = qint32(m_config.streamReceiveWindowSize());
1340 m_maxConcurrentStreams = m_config.maxConcurrentStreams();
1341 encoder.setCompressStrings(m_config.huffmanCompressionEnabled());
1342 decoder.setMaxHeaderListSize(
1343 QHttp2ConfigurationPrivate::get(std::as_const(m_config))->maxHeaderListSize);
1344}
1345
1346void QHttp2Connection::connectionError(Http2Error errorCode, const QString &message,
1347 bool logAsError)
1348{
1349 if (m_connectionAborted)
1350 return;
1351 m_connectionAborted = true;
1352
1353 if (logAsError) {
1354 qCCritical(qHttp2ConnectionLog, "[%p] Connection error: %ls (%d)", this,
1355 qUtf16Printable(message), int(errorCode));
1356 } else {
1357 qCDebug(qHttp2ConnectionLog, "[%p] Closing connection: %ls (%d)", this,
1358 qUtf16Printable(message), int(errorCode));
1359 }
1360
1361 // Mark going away so other code paths will stop creating new streams
1362 m_goingAway = true;
1363 // RFC 9113 5.4.1: An endpoint that encounters a connection error SHOULD
1364 // first send a GOAWAY frame with the last incoming stream ID.
1365 m_lastStreamToProcess = std::min(m_lastIncomingStreamID, m_lastStreamToProcess);
1366 sendGOAWAYFrame(errorCode, m_lastStreamToProcess);
1367
1368 for (QHttp2Stream *stream : std::as_const(m_streams)) {
1369 if (stream && stream->isActive())
1370 stream->finishWithError(errorCode, message);
1371 }
1372 emit errorOccurred(errorCode, message);
1373 // RFC 9113 5.4.1: After sending the GOAWAY frame for an error condition,
1374 // the endpoint MUST close the TCP connection
1375 closeSession();
1376}
1377
1378void QHttp2Connection::closeSession()
1379{
1380 emit connectionClosed();
1381}
1382
1383bool QHttp2Connection::streamWasResetLocally(quint32 streamID) noexcept
1384{
1385 return m_resetStreamIDs.contains(streamID);
1386}
1387
1388void QHttp2Connection::registerStreamAsResetLocally(quint32 streamID)
1389{
1390 // RFC 9113, 6.4: However, after sending the RST_STREAM, the sending endpoint MUST be prepared
1391 // to receive and process additional frames sent on the stream that might have been sent by the
1392 // peer prior to the arrival of the RST_STREAM.
1393
1394 // Store the last 100 stream ids that were reset locally. Frames received on these streams
1395 // are still considered valid for some time (Until 100 other streams are reset locally).
1396 m_resetStreamIDs.append(streamID);
1397 while (m_resetStreamIDs.size() > 100)
1398 m_resetStreamIDs.takeFirst();
1399}
1400
1401bool QHttp2Connection::isInvalidStream(quint32 streamID) noexcept
1402{
1403 auto stream = m_streams.value(streamID, nullptr);
1404 return (!stream || stream->wasResetbyPeer()) && !streamWasResetLocally(streamID);
1405}
1406
1407/*!
1408 When we send a GOAWAY we also send the ID of the last stream we know about
1409 at the time. Any stream that starts after this one is ignored, but we still
1410 have to process HEADERS due to compression state, and DATA due to stream and
1411 connection window size changes.
1412 Other than that - any \a streamID for which this returns true should be
1413 ignored, and deleted at the earliest convenience.
1414*/
1415bool QHttp2Connection::streamIsIgnored(quint32 streamID) const noexcept
1416{
1417 const bool streamIsRemote = (streamID & 1) == (m_connectionType == Type::Client ? 0 : 1);
1418 return Q_UNLIKELY(streamIsRemote && m_lastStreamToProcess < streamID);
1419}
1420
1421bool QHttp2Connection::sendClientPreface()
1422{
1423 QIODevice *socket = getSocket();
1424 // 3.5 HTTP/2 Connection Preface
1425 const qint64 written = socket->write(Http2clientPreface, clientPrefaceLength);
1426 if (written != clientPrefaceLength)
1427 return false;
1428
1429 if (!sendSETTINGS()) {
1430 qCWarning(qHttp2ConnectionLog, "[%p] Failed to send SETTINGS", this);
1431 return false;
1432 }
1433 m_prefaceSent = true;
1434 if (socket->bytesAvailable()) // We ignore incoming data until preface is sent, so handle it now
1435 QMetaObject::invokeMethod(this, &QHttp2Connection::handleReadyRead, Qt::QueuedConnection);
1436 return true;
1437}
1438
1439bool QHttp2Connection::sendServerPreface()
1440{
1441 // We send our SETTINGS frame and ACK the client's SETTINGS frame when it
1442 // arrives.
1443 if (!sendSETTINGS()) {
1444 qCWarning(qHttp2ConnectionLog, "[%p] Failed to send SETTINGS", this);
1445 return false;
1446 }
1447 m_prefaceSent = true;
1448 return true;
1449}
1450
1451bool QHttp2Connection::sendSETTINGS()
1452{
1453 QIODevice *socket = getSocket();
1454 // 6.5 SETTINGS
1455 frameWriter.setOutboundFrame(configurationToSettingsFrame(m_config));
1456 qCDebug(qHttp2ConnectionLog, "[%p] Sending SETTINGS frame, %d bytes", this,
1457 frameWriter.outboundFrame().payloadSize());
1458 Q_ASSERT(frameWriter.outboundFrame().payloadSize());
1459
1460 if (!frameWriter.write(*socket))
1461 return false;
1462
1463 sessionReceiveWindowSize = maxSessionReceiveWindowSize;
1464 // We only send WINDOW_UPDATE for the connection if the size differs from the
1465 // default 64 KB:
1466 const auto delta = maxSessionReceiveWindowSize - defaultSessionWindowSize;
1467 if (delta && !sendWINDOW_UPDATE(connectionStreamID, delta))
1468 return false;
1469
1470 waitingForSettingsACK = true;
1471 return true;
1472}
1473
1474bool QHttp2Connection::sendWINDOW_UPDATE(quint32 streamID, quint32 delta)
1475{
1476 qCDebug(qHttp2ConnectionLog, "[%p] Sending WINDOW_UPDATE frame, stream %d, delta %u", this,
1477 streamID, delta);
1478 frameWriter.start(FrameType::WINDOW_UPDATE, FrameFlag::EMPTY, streamID);
1479 frameWriter.append(delta);
1480 return frameWriter.write(*getSocket());
1481}
1482
1483/*!
1484 \fn quint64 QHttp2Connection::totalBytesReceivedDATA() const
1485
1486 Returns the running total of flow-controlled DATA payload octets received on
1487 this connection. This is the sampling signal for bandwidth-delay-product
1488 based receive-window auto-tuning.
1489*/
1490
1491/*!
1492 Sets the connection-level (session) receive window to \a size on a live
1493 connection. \a size must be between 1 and 2^31-1 inclusive; returns \c false
1494 otherwise.
1495
1496 Growing the window sends a WINDOW_UPDATE on the connection stream. Shrinking
1497 only lowers the ceiling and lets the window drain, since HTTP/2 has no
1498 negative WINDOW_UPDATE.
1499*/
1500bool QHttp2Connection::setSessionReceiveWindowSize(qint32 size)
1501{
1502 if (size <= 0) {
1503 qCWarning(qHttp2ConnectionLog, "[%p] Invalid session receive window size: %d", this, size);
1504 return false;
1505 }
1506 if (size <= maxSessionReceiveWindowSize) {
1507 // No negative WINDOW_UPDATE exists, so just lower the ceiling and let the window drain.
1508 maxSessionReceiveWindowSize = size;
1509 return true;
1510 }
1511 const qint32 delta = size - maxSessionReceiveWindowSize;
1512 maxSessionReceiveWindowSize = size;
1513 sessionReceiveWindowSize += delta;
1514 return sendWINDOW_UPDATE(connectionStreamID, quint32(delta));
1515}
1516
1517void QHttp2Connection::sendClientGracefulShutdownGoaway()
1518{
1519 // Clients send a single GOAWAY. No race condition since they control stream creation
1520 Q_ASSERT(m_connectionType == Type::Client);
1521
1522 if (m_connectionAborted || m_goingAway) {
1523 qCWarning(qHttp2ConnectionLog, "[%p] Client graceful shutdown already in progress", this);
1524 return;
1525 }
1526
1527 m_goingAway = true;
1528 m_gracefulShutdownState = GracefulShutdownState::FinalGOAWAYSent;
1529 m_lastStreamToProcess = m_lastIncomingStreamID;
1530 sendGOAWAYFrame(Http2::HTTP2_NO_ERROR, m_lastStreamToProcess);
1531
1532 maybeCloseOnGoingAway();
1533}
1534
1535void QHttp2Connection::sendInitialServerGracefulShutdownGoaway()
1536{
1537 Q_ASSERT(m_connectionType == Type::Server);
1538 // RFC 9113, 6.8: A server that is attempting to gracefully shut down a
1539 // connection SHOULD send an initial GOAWAY frame with the last stream
1540 // identifier set to 2^31-1 and a NO_ERROR code.
1541 if (m_connectionAborted || m_goingAway) {
1542 qCWarning(qHttp2ConnectionLog, "[%p] Server graceful shutdown already in progress", this);
1543 return;
1544 }
1545
1546 m_goingAway = true;
1547 m_goawayGraceTimer.setRemainingTime(GoawayGracePeriod);
1548 sendGOAWAYFrame(Http2::HTTP2_NO_ERROR, Http2::lastValidStreamID);
1549
1550 // Send PING to measure RTT; handlePING() continues the shutdown on ACK.
1551 // RFC 9113 6.8: After allowing time for any in-flight stream creation
1552 // (at least one round-trip time)
1553 if (sendPing())
1554 m_gracefulShutdownState = GracefulShutdownState::AwaitingShutdownPing;
1555 else
1556 m_gracefulShutdownState = GracefulShutdownState::AwaitingPriorPing;
1557}
1558
1559void QHttp2Connection::sendFinalServerGracefulShutdownGoaway()
1560{
1561 if (m_connectionAborted || !m_goingAway) {
1562 qCWarning(qHttp2ConnectionLog, "[%p] Server graceful shutdown not in progress", this);
1563 return;
1564 }
1565 m_gracefulShutdownState = GracefulShutdownState::FinalGOAWAYSent;
1566 m_lastStreamToProcess = m_lastIncomingStreamID;
1567 sendGOAWAYFrame(Http2::HTTP2_NO_ERROR, m_lastStreamToProcess);
1568 maybeCloseOnGoingAway();
1569}
1570
1571bool QHttp2Connection::sendGOAWAYFrame(Http2::Http2Error errorCode, quint32 lastStreamID)
1572{
1573 QIODevice *socket = getSocket();
1574 if (!socket || !socket->isOpen())
1575 return false;
1576
1577 qCDebug(qHttp2ConnectionLog, "[%p] Sending GOAWAY frame, error code %u, last stream %u", this,
1578 errorCode, lastStreamID);
1579
1580 frameWriter.start(FrameType::GOAWAY, FrameFlag::EMPTY,
1581 Http2PredefinedParameters::connectionStreamID);
1582 frameWriter.append(lastStreamID);
1583 frameWriter.append(quint32(errorCode));
1584 return frameWriter.write(*socket);
1585}
1586
1587void QHttp2Connection::maybeCloseOnGoingAway()
1588{
1589 // Only close if we've reached the final phase of graceful shutdown
1590 // For the sender: after FinalGOAWAYSent
1591 // For the receiver: after receiving GOAWAY and all our streams are done
1592 if (m_connectionAborted || !m_goingAway) {
1593 qCDebug(qHttp2ConnectionLog, "[%p] Connection close deferred, graceful shutdown not active",
1594 this);
1595 return;
1596 }
1597
1598 // For graceful shutdown initiator, only close after final GOAWAY is sent
1599 if (m_gracefulShutdownState == GracefulShutdownState::AwaitingShutdownPing)
1600 return; // Still waiting for RTT measurement before final GOAWAY
1601
1602 const auto streamIsActive = [](const QPointer<QHttp2Stream> &stream) {
1603 return stream && stream->isActive();
1604 };
1605
1606 if (std::none_of(m_streams.cbegin(), m_streams.cend(), streamIsActive)) {
1607 qCDebug(qHttp2ConnectionLog, "[%p] All streams closed, closing connection", this);
1608 closeSession();
1609 }
1610}
1611
1612bool QHttp2Connection::sendSETTINGS_ACK()
1613{
1614 frameWriter.start(FrameType::SETTINGS, FrameFlag::ACK, Http2::connectionStreamID);
1615 return frameWriter.write(*getSocket());
1616}
1617
1618void QHttp2Connection::handleDATA()
1619{
1620 Q_ASSERT(inboundFrame.type() == FrameType::DATA);
1621
1622 const auto streamID = inboundFrame.streamID();
1623
1624 // RFC9113, 6.1: An endpoint that receives an unexpected stream identifier MUST respond
1625 // with a connection error.
1626 if (streamID == connectionStreamID)
1627 return connectionError(PROTOCOL_ERROR, u"DATA on the connection stream"_s);
1628
1629 if (isInvalidStream(streamID))
1630 return connectionError(ENHANCE_YOUR_CALM, u"DATA on invalid stream"_s);
1631
1632 QHttp2Stream *stream = nullptr;
1633 if (!streamWasResetLocally(streamID)) {
1634 stream = getStream(streamID);
1635 // RFC9113, 6.1: If a DATA frame is received whose stream is not in the "open" or
1636 // "half-closed (local)" state, the recipient MUST respond with a stream error.
1637 if (stream->state() == QHttp2Stream::State::HalfClosedRemote
1638 || stream->state() == QHttp2Stream::State::Closed) {
1639 return stream->streamError(Http2Error::STREAM_CLOSED, u"Data on closed stream"_s);
1640 }
1641 }
1642
1643 if (inboundFrame.payloadSize() > m_config.maxFrameSize()) {
1644 qCDebug(qHttp2ConnectionLog,
1645 "[%p] Received DATA frame with payload size %u, "
1646 "but SETTINGS_MAX_FRAME_SIZE is %u, sending FRAME_SIZE_ERROR",
1647 this, inboundFrame.payloadSize(), m_config.maxFrameSize());
1648 if (stream)
1649 return stream->streamError(Http2Error::FRAME_SIZE_ERROR,
1650 u"DATA payload size exceeds SETTINGS_MAX_FRAME_SIZE"_s);
1651 return connectionError(FRAME_SIZE_ERROR, u"DATA payload size exceeds SETTINGS_MAX_FRAME_SIZE"_s);
1652 }
1653
1654 if (qint32(inboundFrame.payloadSize()) > sessionReceiveWindowSize) {
1655 qCDebug(qHttp2ConnectionLog,
1656 "[%p] Received DATA frame with payload size %u, "
1657 "but recvWindow is %d, sending FLOW_CONTROL_ERROR",
1658 this, inboundFrame.payloadSize(), sessionReceiveWindowSize);
1659 return connectionError(FLOW_CONTROL_ERROR, u"Flow control error"_s);
1660 }
1661
1662 sessionReceiveWindowSize -= inboundFrame.payloadSize();
1663 m_totalBytesReceivedDATA += inboundFrame.payloadSize();
1664
1665 if (stream)
1666 stream->handleDATA(inboundFrame);
1667
1668
1669 if (inboundFrame.flags().testFlag(FrameFlag::END_STREAM)) {
1670 const bool ignoreData = stream && streamIsIgnored(stream->streamID());
1671 if (!ignoreData) {
1672 emit receivedEND_STREAM(streamID);
1673 } else {
1674 // Stream opened after our GOAWAY cut-off. We would just drop the
1675 // data, but needed to handle it enough to track sizes of streams and
1676 // connection windows. Since we've now taken care of that, we can
1677 // at last close and delete it.
1678 stream->setState(QHttp2Stream::State::Closed);
1679 delete stream;
1680 }
1681 }
1682
1683 if (sessionReceiveWindowSize < maxSessionReceiveWindowSize / 2) {
1684 // @future[consider]: emit signal instead
1685 QMetaObject::invokeMethod(this, &QHttp2Connection::sendWINDOW_UPDATE, Qt::QueuedConnection,
1686 quint32(connectionStreamID),
1687 quint32(maxSessionReceiveWindowSize - sessionReceiveWindowSize));
1688 sessionReceiveWindowSize = maxSessionReceiveWindowSize;
1689 }
1690}
1691
1692void QHttp2Connection::handleHEADERS()
1693{
1694 Q_ASSERT(inboundFrame.type() == FrameType::HEADERS);
1695
1696 const auto streamID = inboundFrame.streamID();
1697 qCDebug(qHttp2ConnectionLog, "[%p] Received HEADERS frame on stream %d, end stream? %s", this,
1698 streamID, inboundFrame.flags().testFlag(Http2::FrameFlag::END_STREAM) ? "yes" : "no");
1699
1700 // RFC 9113, 6.2: If a HEADERS frame is received whose Stream Identifier field is 0x00, the
1701 // recipient MUST respond with a connection error.
1702 if (streamID == connectionStreamID)
1703 return connectionError(PROTOCOL_ERROR, u"HEADERS on 0x0 stream"_s);
1704
1705 if (inboundFrame.payloadSize() > m_config.maxFrameSize()) {
1706 qCDebug(qHttp2ConnectionLog,
1707 "[%p] Received HEADERS frame with payload size %u, "
1708 "but SETTINGS_MAX_FRAME_SIZE is %u, sending FRAME_SIZE_ERROR",
1709 this, inboundFrame.payloadSize(), m_config.maxFrameSize());
1710 return connectionError(Http2Error::FRAME_SIZE_ERROR,
1711 u"HEADERS payload size exceeds SETTINGS_MAX_FRAME_SIZE"_s);
1712 }
1713
1714 const bool isClient = m_connectionType == Type::Client;
1715 const bool isClientInitiatedStream = !!(streamID & 1);
1716 const bool isRemotelyInitiatedStream = isClient ^ isClientInitiatedStream;
1717
1718 if (isRemotelyInitiatedStream && streamID > m_lastIncomingStreamID) {
1719 bool streamCountIsOk = size_t(m_maxConcurrentStreams) > size_t(numActiveRemoteStreams());
1720 QHttp2Stream *newStream = createStreamInternal_impl(streamID);
1721 Q_ASSERT(newStream);
1722 m_lastIncomingStreamID = streamID;
1723
1724 if (!streamCountIsOk) {
1725 newStream->setState(QHttp2Stream::State::Open);
1726 newStream->streamError(PROTOCOL_ERROR, u"Max concurrent streams reached"_s);
1727
1728 emit incomingStreamErrorOccured(CreateStreamError::MaxConcurrentStreamsReached);
1729 return;
1730 }
1731
1732 qCDebug(qHttp2ConnectionLog, "[%p] New incoming stream %d", this, streamID);
1733 if (!streamIsIgnored(newStream->streamID())) {
1734 emit newIncomingStream(newStream);
1735 } else if (m_goawayGraceTimer.hasExpired()) {
1736 // We gave the peer some time to handle the GOAWAY message, but they have started a new
1737 // stream, so we error out.
1738 connectionError(Http2Error::PROTOCOL_ERROR, u"Peer refused to GOAWAY."_s);
1739 return;
1740 }
1741 } else if (streamWasResetLocally(streamID)) {
1742 qCDebug(qHttp2ConnectionLog,
1743 "[%p] Received HEADERS on previously locally reset stream %d (must process but ignore)",
1744 this, streamID);
1745 // nop
1746 } else if (auto it = m_streams.constFind(streamID); it == m_streams.cend()) {
1747 // RFC 9113, 6.2: HEADERS frames MUST be associated with a stream.
1748 // A connection error is not required but it seems to be the right thing to do.
1749 qCDebug(qHttp2ConnectionLog, "[%p] Received HEADERS on non-existent stream %d", this,
1750 streamID);
1751 return connectionError(PROTOCOL_ERROR, u"HEADERS on invalid stream"_s);
1752 } else if (isInvalidStream(streamID)) {
1753 // RFC 9113 6.4: After receiving a RST_STREAM on a stream, the receiver MUST NOT send
1754 // additional frames for that stream
1755 qCDebug(qHttp2ConnectionLog, "[%p] Received HEADERS on reset stream %d", this, streamID);
1756 return connectionError(ENHANCE_YOUR_CALM, u"HEADERS on invalid stream"_s);
1757 }
1758
1759 const auto flags = inboundFrame.flags();
1760 if (flags.testFlag(FrameFlag::PRIORITY)) {
1761 qCDebug(qHttp2ConnectionLog, "[%p] HEADERS frame on stream %d has PRIORITY flag", this,
1762 streamID);
1763 handlePRIORITY();
1764 }
1765
1766 const bool endHeaders = flags.testFlag(FrameFlag::END_HEADERS);
1767 continuedFrames.clear();
1768 m_headerBlockSize = 0;
1769 if (!validateHeaderListSize(inboundFrame))
1770 return;
1771 continuedFrames.push_back(std::move(inboundFrame));
1772 if (!endHeaders) {
1773 continuationExpected = true;
1774 return;
1775 }
1776
1777 handleContinuedHEADERS();
1778}
1779
1780void QHttp2Connection::handlePRIORITY()
1781{
1782 Q_ASSERT(inboundFrame.type() == FrameType::PRIORITY
1783 || inboundFrame.type() == FrameType::HEADERS);
1784
1785 const auto streamID = inboundFrame.streamID();
1786 if (streamIsIgnored(streamID))
1787 return;
1788
1789 // RFC 9913, 6.3: If a PRIORITY frame is received with a stream identifier of 0x00, the
1790 // recipient MUST respond with a connection error
1791 if (streamID == connectionStreamID)
1792 return connectionError(PROTOCOL_ERROR, u"PRIORITY on 0x0 stream"_s);
1793
1794 // RFC 9113 6.4: After receiving a RST_STREAM on a stream, the receiver MUST NOT send
1795 // additional frames for that stream
1796 if (isInvalidStream(streamID))
1797 return connectionError(ENHANCE_YOUR_CALM, u"PRIORITY on invalid stream"_s);
1798
1799 // RFC 9913, 6.3: A PRIORITY frame with a length other than 5 octets MUST be treated as a
1800 // stream error (Section 5.4.2) of type FRAME_SIZE_ERROR.
1801 // checked in Frame::validateHeader()
1802 Q_ASSERT(inboundFrame.type() != FrameType::PRIORITY || inboundFrame.payloadSize() == 5);
1803
1804 quint32 streamDependency = 0;
1805 uchar weight = 0;
1806 const bool noErr = inboundFrame.priority(&streamDependency, &weight);
1807 Q_UNUSED(noErr);
1808 Q_ASSERT(noErr);
1809
1810 const bool exclusive = streamDependency & 0x80000000;
1811 streamDependency &= ~0x80000000;
1812
1813 // Ignore this for now ...
1814 // Can be used for streams (re)prioritization - 5.3
1815 Q_UNUSED(exclusive);
1816 Q_UNUSED(weight);
1817}
1818
1819void QHttp2Connection::handleRST_STREAM()
1820{
1821 Q_ASSERT(inboundFrame.type() == FrameType::RST_STREAM);
1822
1823 const auto streamID = inboundFrame.streamID();
1824 if (streamIsIgnored(streamID))
1825 return;
1826
1827 // RFC 9113, 6.4: RST_STREAM frames MUST be associated with a stream.
1828 // If a RST_STREAM frame is received with a stream identifier of 0x0,
1829 // the recipient MUST treat this as a connection error (Section 5.4.1)
1830 // of type PROTOCOL_ERROR.
1831 if (streamID == connectionStreamID)
1832 return connectionError(PROTOCOL_ERROR, u"RST_STREAM on 0x0"_s);
1833
1834 // RFC 9113, 6.4: A RST_STREAM frame with a length other than 4 octets MUST be treated as a
1835 // connection error (Section 5.4.1) of type FRAME_SIZE_ERROR.
1836 // checked in Frame::validateHeader()
1837 Q_ASSERT(inboundFrame.payloadSize() == 4);
1838
1839 const auto error = qFromBigEndian<quint32>(inboundFrame.dataBegin());
1840 if (QPointer<QHttp2Stream> stream = m_streams.value(streamID))
1841 emit stream->rstFrameReceived(error);
1842
1843 // Verify that whatever stream is being RST'd is not in the idle state:
1844 const quint32 lastRelevantStreamID = [this, streamID]() {
1845 quint32 peerMask = m_connectionType == Type::Client ? 0 : 1;
1846 return ((streamID & 1) == peerMask) ? m_lastIncomingStreamID : m_nextStreamID - 2;
1847 }();
1848 if (streamID > lastRelevantStreamID) {
1849 // "RST_STREAM frames MUST NOT be sent for a stream
1850 // in the "idle" state. .. the recipient MUST treat this
1851 // as a connection error (Section 5.4.1) of type PROTOCOL_ERROR."
1852 return connectionError(PROTOCOL_ERROR, u"RST_STREAM on idle stream"_s);
1853 }
1854
1855 Q_ASSERT(inboundFrame.dataSize() == 4);
1856
1857 if (QPointer<QHttp2Stream> stream = m_streams.value(streamID))
1858 stream->handleRST_STREAM(inboundFrame);
1859}
1860
1861void QHttp2Connection::handleSETTINGS()
1862{
1863 // 6.5 SETTINGS.
1864 Q_ASSERT(inboundFrame.type() == FrameType::SETTINGS);
1865
1866 // RFC 9113, 6.5: If an endpoint receives a SETTINGS frame whose Stream Identifier field is
1867 // anything other than 0x00, the endpoint MUST respond with a connection error
1868 if (inboundFrame.streamID() != connectionStreamID)
1869 return connectionError(PROTOCOL_ERROR, u"SETTINGS on invalid stream"_s);
1870
1871 if (inboundFrame.flags().testFlag(FrameFlag::ACK)) {
1872 // RFC 9113, 6.5: Receipt of a SETTINGS frame with the ACK flag set and a length field
1873 // value other than 0 MUST be treated as a connection error
1874 if (inboundFrame.payloadSize())
1875 return connectionError(FRAME_SIZE_ERROR, u"SETTINGS ACK with data"_s);
1876 if (!waitingForSettingsACK)
1877 return connectionError(PROTOCOL_ERROR, u"unexpected SETTINGS ACK"_s);
1878 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS ACK", this);
1879 waitingForSettingsACK = false;
1880 return;
1881 }
1882 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS frame", this);
1883
1884 if (inboundFrame.dataSize()) {
1885 // RFC 9113, 6.5: A SETTINGS frame with a length other than a multiple of 6 octets MUST be
1886 // treated as a connection error (Section 5.4.1) of type FRAME_SIZE_ERROR.
1887 // checked in Frame::validateHeader()
1888 Q_ASSERT(inboundFrame.payloadSize() % 6 == 0);
1889
1890 auto src = inboundFrame.dataBegin();
1891 for (const uchar *end = src + inboundFrame.dataSize(); src != end; src += 6) {
1892 const Settings identifier = Settings(qFromBigEndian<quint16>(src));
1893 const quint32 intVal = qFromBigEndian<quint32>(src + 2);
1894 if (!acceptSetting(identifier, intVal)) {
1895 // If not accepted - we finish with connectionError.
1896 qCDebug(qHttp2ConnectionLog, "[%p] Received an unacceptable setting, %u, %u", this,
1897 quint32(identifier), intVal);
1898 return; // connectionError already called in acceptSetting.
1899 }
1900 }
1901 }
1902
1903 qCDebug(qHttp2ConnectionLog, "[%p] Sending SETTINGS ACK", this);
1904 sendSETTINGS_ACK();
1905 emit settingsFrameReceived();
1906}
1907
1908void QHttp2Connection::handlePUSH_PROMISE()
1909{
1910 // 6.6 PUSH_PROMISE.
1911 Q_ASSERT(inboundFrame.type() == FrameType::PUSH_PROMISE);
1912
1913 // RFC 9113, 6.6: PUSH_PROMISE MUST NOT be sent if the SETTINGS_ENABLE_PUSH setting of the peer
1914 // endpoint is set to 0. An endpoint that has set this setting and has received acknowledgment
1915 // MUST treat the receipt of a PUSH_PROMISE frame as a connection error
1916 if (!pushPromiseEnabled && !waitingForSettingsACK) {
1917 // This means, server ACKed our 'NO PUSH',
1918 // but sent us PUSH_PROMISE anyway.
1919 return connectionError(PROTOCOL_ERROR, u"unexpected PUSH_PROMISE frame"_s);
1920 }
1921
1922 // RFC 9113, 6.6: If the Stream Identifier field specifies the value 0x00, a recipient MUST
1923 // respond with a connection error.
1924 const auto streamID = inboundFrame.streamID();
1925 if (streamID == connectionStreamID)
1926 return connectionError(PROTOCOL_ERROR, u"PUSH_PROMISE with invalid associated stream (0x0)"_s);
1927
1928 auto it = m_streams.constFind(streamID);
1929#if 0 // Needs to be done after some timeout in case the stream has only just been reset
1930 if (it != m_streams.constEnd()) {
1931 QHttp2Stream *associatedStream = it->get();
1932 if (associatedStream->state() != QHttp2Stream::State::Open
1933 && associatedStream->state() != QHttp2Stream::State::HalfClosedLocal) {
1934 // Cause us to error out below:
1935 it = m_streams.constEnd();
1936 }
1937 }
1938#endif
1939 // RFC 9113, 6.6: PUSH_PROMISE frames MUST only be sent on a peer-initiated stream that
1940 // is in either the "open" or "half-closed (remote)" state.
1941
1942 // I.e. If you are the server then the client must have initiated the stream you are sending
1943 // the promise on. And since this is about _sending_ we have to invert "Remote" to "Local"
1944 // because we are receiving.
1945 if (it == m_streams.constEnd() || it->isNull())
1946 return connectionError(ENHANCE_YOUR_CALM, u"PUSH_PROMISE with invalid associated stream"_s);
1947 if ((m_connectionType == Type::Client && (streamID & 1) == 0) ||
1948 (m_connectionType == Type::Server && (streamID & 1) == 1)) {
1949 return connectionError(ENHANCE_YOUR_CALM, u"PUSH_PROMISE with invalid associated stream"_s);
1950 }
1951 if ((*it)->state() != QHttp2Stream::State::Open &&
1952 (*it)->state() != QHttp2Stream::State::HalfClosedLocal) {
1953 return connectionError(ENHANCE_YOUR_CALM, u"PUSH_PROMISE with invalid associated stream"_s);
1954 }
1955
1956 // RFC 9113, 6.6: The promised stream identifier MUST be a valid choice for the
1957 // next stream sent by the sender
1958 const auto reservedID = qFromBigEndian<quint32>(inboundFrame.dataBegin());
1959 if ((reservedID & 1) || reservedID <= m_lastIncomingStreamID || reservedID > lastValidStreamID)
1960 return connectionError(PROTOCOL_ERROR, u"PUSH_PROMISE with invalid promised stream ID"_s);
1961
1962 bool streamCountIsOk = size_t(m_maxConcurrentStreams) > size_t(numActiveRemoteStreams());
1963 // RFC 9113, 6.6: A receiver MUST treat the receipt of a PUSH_PROMISE that promises an
1964 // illegal stream identifier (Section 5.1.1) as a connection error
1965 auto *stream = createStreamInternal_impl(reservedID);
1966 if (!stream)
1967 return connectionError(PROTOCOL_ERROR, u"PUSH_PROMISE with already active stream ID"_s);
1968 m_lastIncomingStreamID = reservedID;
1969 stream->setState(QHttp2Stream::State::ReservedRemote);
1970
1971 if (!streamCountIsOk) {
1972 stream->streamError(PROTOCOL_ERROR, u"Max concurrent streams reached"_s);
1973 emit incomingStreamErrorOccured(CreateStreamError::MaxConcurrentStreamsReached);
1974 return;
1975 }
1976
1977 // "ignoring a PUSH_PROMISE frame causes the stream state to become
1978 // indeterminate" - let's send RST_STREAM frame with REFUSE_STREAM code.
1979 if (!pushPromiseEnabled)
1980 return stream->streamError(REFUSE_STREAM, u"PUSH_PROMISE not enabled but ignored"_s);
1981
1982 // RFC 9113, 6.6: The total number of padding octets is determined by the value of the Pad
1983 // Length field. If the length of the padding is the length of the frame payload or greater,
1984 // the recipient MUST treat this as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
1985 // checked in Frame::validateHeader()
1986 Q_ASSERT(inboundFrame.dataSize() > inboundFrame.padding());
1987 const bool endHeaders = inboundFrame.flags().testFlag(FrameFlag::END_HEADERS);
1988 continuedFrames.clear();
1989 m_headerBlockSize = 0;
1990 if (!validateHeaderListSize(inboundFrame))
1991 return;
1992 continuedFrames.push_back(std::move(inboundFrame));
1993
1994 if (!endHeaders) {
1995 continuationExpected = true;
1996 return;
1997 }
1998
1999 handleContinuedHEADERS();
2000}
2001
2002void QHttp2Connection::handlePING()
2003{
2004 Q_ASSERT(inboundFrame.type() == FrameType::PING);
2005
2006 // RFC 9113, 6.7: PING frames are not associated with any individual stream. If a PING frame is
2007 // received with a Stream Identifier field value other than 0x00, the recipient MUST respond
2008 // with a connection error
2009 if (inboundFrame.streamID() != connectionStreamID)
2010 return connectionError(PROTOCOL_ERROR, u"PING on invalid stream"_s);
2011
2012 // Receipt of a PING frame with a length field value other than 8 MUST be treated
2013 // as a connection error (Section 5.4.1) of type FRAME_SIZE_ERROR.
2014 // checked in Frame::validateHeader()
2015 Q_ASSERT(inboundFrame.payloadSize() == 8);
2016
2017 if (inboundFrame.flags() & FrameFlag::ACK) {
2018 QByteArrayView pingSignature(reinterpret_cast<const char *>(inboundFrame.dataBegin()), 8);
2019 if (!m_lastPingSignature.has_value()) {
2020 emit pingFrameReceived(PingState::PongNoPingSent);
2021 qCWarning(qHttp2ConnectionLog, "[%p] PING with ACK received but no PING was sent.", this);
2022 } else if (pingSignature != m_lastPingSignature) {
2023 emit pingFrameReceived(PingState::PongSignatureChanged);
2024 qCWarning(qHttp2ConnectionLog, "[%p] PING signature does not match the last PING.", this);
2025 } else {
2026 emit pingFrameReceived(PingState::PongSignatureIdentical);
2027 }
2028 m_lastPingSignature.reset();
2029
2030 // Handle sendInitialServerGracefulShutdownGoaway()
2031 if (m_gracefulShutdownState == GracefulShutdownState::AwaitingShutdownPing) {
2032 sendFinalServerGracefulShutdownGoaway();
2033 } else if (m_gracefulShutdownState == GracefulShutdownState::AwaitingPriorPing) {
2034 // Prior PING completed, now send our RTT measurement PING. This shouldn't fail!
2035 m_gracefulShutdownState = GracefulShutdownState::AwaitingShutdownPing;
2036 [[maybe_unused]] const bool ok = sendPing();
2037 Q_ASSERT(ok);
2038 }
2039
2040 return;
2041 } else {
2042 emit pingFrameReceived(PingState::Ping);
2043
2044 }
2045
2046
2047 frameWriter.start(FrameType::PING, FrameFlag::ACK, connectionStreamID);
2048 frameWriter.append(inboundFrame.dataBegin(), inboundFrame.dataBegin() + 8);
2049 frameWriter.write(*getSocket());
2050}
2051
2052void QHttp2Connection::handleGOAWAY()
2053{
2054 // 6.8 GOAWAY
2055
2056 Q_ASSERT(inboundFrame.type() == FrameType::GOAWAY);
2057 // RFC 9113, 6.8: An endpoint MUST treat a GOAWAY frame with a stream identifier
2058 // other than 0x0 as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
2059 if (inboundFrame.streamID() != connectionStreamID)
2060 return connectionError(PROTOCOL_ERROR, u"GOAWAY on invalid stream"_s);
2061
2062 // RFC 9113, 6.8:
2063 // Reserved (1) + Last-Stream-ID (31) + Error Code (32) + Additional Debug Data (..)
2064 // checked in Frame::validateHeader()
2065 Q_ASSERT(inboundFrame.payloadSize() >= 8);
2066
2067 const uchar *const src = inboundFrame.dataBegin();
2068 // RFC 9113, 4.1: 31-bit Stream ID; lastValidStreamID(0x7FFFFFFF) masks out the reserved MSB
2069 const quint32 lastStreamID = qFromBigEndian<quint32>(src) & lastValidStreamID;
2070 const Http2Error errorCode = Http2Error(qFromBigEndian<quint32>(src + 4));
2071
2072 // 6.8 "the GOAWAY contains the stream identifier of the last peer-initiated stream that was
2073 // or might be processed on the sending endpoint in this connection."
2074 // Alternatively, they can specify 0 as the last stream ID, meaning they are not intending to
2075 // process any remaining stream(s).
2076 const quint32 LocalMask = m_connectionType == Type::Client ? 1 : 0;
2077 // The stream must match the LocalMask, meaning we initiated it, for the last stream ID to make
2078 // sense - they are not processing their own streams.
2079 if (lastStreamID != 0 && (lastStreamID & 0x1) != LocalMask)
2080 return connectionError(PROTOCOL_ERROR, u"GOAWAY with invalid last stream ID"_s);
2081
2082 // 6.8 - An endpoint MAY send multiple GOAWAY frames if circumstances
2083 // change. Endpoints MUST NOT increase the value they send in the last
2084 // stream identifier
2085 if (m_lastGoAwayLastStreamID && lastStreamID > *m_lastGoAwayLastStreamID)
2086 return connectionError(PROTOCOL_ERROR, u"Repeated GOAWAY with invalid last stream ID"_s);
2087 m_lastGoAwayLastStreamID = lastStreamID;
2088
2089 qCDebug(qHttp2ConnectionLog, "[%p] Received GOAWAY frame, error code %u, last stream %u",
2090 this, errorCode, lastStreamID);
2091 m_goingAway = true;
2092
2093 emit receivedGOAWAY(errorCode, lastStreamID);
2094
2095 if (errorCode == HTTP2_NO_ERROR) {
2096 // Graceful GOAWAY (NO_ERROR): Only cancel streams the peer explicitly won't process
2097 // (those with IDs > lastStreamID). Streams with ID <= lastStreamID can still complete.
2098 // '0' can be used in the special case that no streams at all were or will be processed.
2099 const quint32 firstPossibleStream = m_connectionType == Type::Client ? 1 : 2;
2100 const quint32 firstCancelledStream = lastStreamID ? lastStreamID + 2 : firstPossibleStream;
2101 Q_ASSERT((firstCancelledStream & 0x1) == LocalMask);
2102 for (quint32 id = firstCancelledStream; id < m_nextStreamID; id += 2) {
2103 QHttp2Stream *stream = m_streams.value(id, nullptr);
2104 if (stream && stream->isActive())
2105 stream->finishWithError(errorCode, u"Received GOAWAY"_s);
2106 }
2107 maybeCloseOnGoingAway(); // check if we can close now
2108 } else {
2109 // RFC 9113, 5.4.1: After sending the GOAWAY frame for an error
2110 // condition, the endpoint MUST close the TCP connection.
2111 // As the peer is closing the connection immediately, they won't
2112 // process any more data, so we close the connection here already.
2113 m_connectionAborted = true;
2114 for (QHttp2Stream *stream : std::as_const(m_streams)) {
2115 if (stream && stream->isActive())
2116 stream->finishWithError(errorCode, u"Received GOAWAY"_s);
2117 }
2118 closeSession();
2119 }
2120}
2121
2122void QHttp2Connection::handleWINDOW_UPDATE()
2123{
2124 Q_ASSERT(inboundFrame.type() == FrameType::WINDOW_UPDATE);
2125
2126 const quint32 delta = qFromBigEndian<quint32>(inboundFrame.dataBegin());
2127 // RFC 9113, 6.9: A receiver MUST treat the receipt of a WINDOW_UPDATE frame with a
2128 // flow-control window increment of 0 as a stream error (Section 5.4.2) of type PROTOCOL_ERROR;
2129 // errors on the connection flow-control window MUST be treated as a connection error
2130 const bool valid = delta && delta <= quint32(std::numeric_limits<qint32>::max());
2131 const auto streamID = inboundFrame.streamID();
2132 if (streamIsIgnored(streamID))
2133 return;
2134
2135 // RFC 9113, 6.9: A WINDOW_UPDATE frame with a length other than 4 octets MUST be treated
2136 // as a connection error (Section 5.4.1) of type FRAME_SIZE_ERROR.
2137 // checked in Frame::validateHeader()
2138 Q_ASSERT(inboundFrame.payloadSize() == 4);
2139
2140 qCDebug(qHttp2ConnectionLog(), "[%p] Received WINDOW_UPDATE, stream %d, delta %d", this,
2141 streamID, delta);
2142 if (streamID == connectionStreamID) {
2143 if (!valid)
2144 return connectionError(PROTOCOL_ERROR, u"WINDOW_UPDATE invalid delta"_s);
2145 qint32 sum = 0;
2146 // RFC 9113, 6.9.1: a WINDOW_UPDATE that pushes the connection window past 2^31-1 is a
2147 // connection error of type FLOW_CONTROL_ERROR (the sender sends GOAWAY).
2148 if (qAddOverflow(sessionSendWindowSize, qint32(delta), &sum))
2149 return connectionError(FLOW_CONTROL_ERROR, u"WINDOW_UPDATE exceeds maximum window"_s);
2150 sessionSendWindowSize = sum;
2151
2152 // Stream may have been unblocked, so maybe try to write again:
2153 const auto blockedStreams = std::exchange(m_blockedStreams, {});
2154 for (quint32 blockedStreamID : blockedStreams) {
2155 const QPointer<QHttp2Stream> stream = m_streams.value(blockedStreamID);
2156 if (!stream || !stream->isActive() || !stream->isUploadingDATA())
2157 continue;
2158 if (stream->isUploadBlocked()) {
2159 m_blockedStreams.insert(blockedStreamID);
2160
2161
2162 } else {
2163 QMetaObject::invokeMethod(stream, &QHttp2Stream::maybeResumeUpload,
2164 Qt::QueuedConnection);
2165 }
2166 }
2167 } else {
2168 QHttp2Stream *stream = m_streams.value(streamID);
2169 if (!stream || !stream->isActive()) {
2170 // WINDOW_UPDATE on closed streams can be ignored.
2171 qCDebug(qHttp2ConnectionLog, "[%p] Received WINDOW_UPDATE on closed stream %d", this,
2172 streamID);
2173 return;
2174 }
2175 if (!valid)
2176 return stream->streamError(PROTOCOL_ERROR, u"WINDOW_UPDATE invalid delta"_s);
2177
2178 stream->handleWINDOW_UPDATE(inboundFrame);
2179 }
2180}
2181
2182void QHttp2Connection::handleCONTINUATION()
2183{
2184 Q_ASSERT(inboundFrame.type() == FrameType::CONTINUATION);
2185 if (inboundFrame.payloadSize() > m_config.maxFrameSize()) {
2186 qCDebug(qHttp2ConnectionLog,
2187 "[%p] Received CONTINUATION frame with payload size %u, "
2188 "but SETTINGS_MAX_FRAME_SIZE is %u, sending FRAME_SIZE_ERROR",
2189 this, inboundFrame.payloadSize(), m_config.maxFrameSize());
2190 return connectionError(Http2Error::FRAME_SIZE_ERROR,
2191 u"CONTINUATION payload size exceeds SETTINGS_MAX_FRAME_SIZE"_s);
2192 }
2193 auto streamID = inboundFrame.streamID();
2194 qCDebug(qHttp2ConnectionLog,
2195 "[%p] Received CONTINUATION frame on stream %d, end stream? %s", this, streamID,
2196 inboundFrame.flags().testFlag(Http2::FrameFlag::END_STREAM) ? "yes" : "no");
2197 if (continuedFrames.empty())
2198 return connectionError(PROTOCOL_ERROR,
2199 u"CONTINUATION without a preceding HEADERS or PUSH_PROMISE"_s);
2200 if (!continuationExpected)
2201 return connectionError(PROTOCOL_ERROR,
2202 u"CONTINUATION after a frame with the END_HEADERS flag set"_s);
2203
2204 if (inboundFrame.streamID() != continuedFrames.front().streamID())
2205 return connectionError(PROTOCOL_ERROR, u"CONTINUATION on invalid stream"_s);
2206
2207 const bool endHeaders = inboundFrame.flags().testFlag(FrameFlag::END_HEADERS);
2208 // No reset here: this frame continues the block begun by HEADERS/PUSH_PROMISE,
2209 // so it must add to the running total rather than start a new one.
2210 if (!validateHeaderListSize(inboundFrame))
2211 return;
2212 continuedFrames.push_back(std::move(inboundFrame));
2213
2214 if (!endHeaders)
2215 return;
2216
2217 continuationExpected = false;
2218 handleContinuedHEADERS();
2219}
2220
2221bool QHttp2Connection::validateHeaderListSize(const Frame &frame)
2222{
2223 const quint32 limit =
2224 QHttp2ConfigurationPrivate::get(std::as_const(m_config))->maxHeaderListSize;
2225 if (limit == std::numeric_limits<quint32>::max())
2226 return true;
2227 // Conservative pre-decode resource check. The compressed header block cannot
2228 // exceed the decoded header list size, so decoding would inevitably exceed
2229 // SETTINGS_MAX_HEADER_LIST_SIZE. Reject the request early with
2230 // ENHANCE_YOUR_CALM rather than attempting HPACK decoding.
2231 m_headerBlockSize += frame.hpackBlockSize();
2232 if (m_headerBlockSize > limit) {
2233 connectionError(ENHANCE_YOUR_CALM, u"Header list size limit exceeded"_s);
2234 return false;
2235 }
2236 return true;
2237}
2238
2239void QHttp2Connection::handleContinuedHEADERS()
2240{
2241 // 'Continued' HEADERS can be: the initial HEADERS/PUSH_PROMISE frame
2242 // with/without END_HEADERS flag set plus, if no END_HEADERS flag,
2243 // a sequence of one or more CONTINUATION frames.
2244 Q_ASSERT(!continuedFrames.empty());
2245 const auto firstFrameType = continuedFrames[0].type();
2246 Q_ASSERT(firstFrameType == FrameType::HEADERS || firstFrameType == FrameType::PUSH_PROMISE);
2247
2248 const auto streamID = continuedFrames[0].streamID();
2249
2250 const auto streamIt = m_streams.constFind(streamID);
2251 if (firstFrameType == FrameType::HEADERS) {
2252 if (streamIt != m_streams.cend() && !streamWasResetLocally(streamID)) {
2253 QHttp2Stream *stream = streamIt.value();
2254 if (stream->state() != QHttp2Stream::State::HalfClosedLocal
2255 && stream->state() != QHttp2Stream::State::ReservedRemote
2256 && stream->state() != QHttp2Stream::State::Idle
2257 && stream->state() != QHttp2Stream::State::Open) {
2258 // We can receive HEADERS on streams initiated by our requests
2259 // (these streams are in halfClosedLocal or open state) or
2260 // remote-reserved streams from a server's PUSH_PROMISE.
2261 return stream->streamError(PROTOCOL_ERROR, u"HEADERS on invalid stream"_s);
2262 }
2263 }
2264 // Else: we cannot just ignore our peer's HEADERS frames - they change
2265 // HPACK context - even though the stream was reset; apparently the peer
2266 // has yet to see the reset.
2267 }
2268
2269 std::vector<uchar> hpackBlock(assemble_hpack_block(continuedFrames));
2270 const bool hasHeaderFields = !hpackBlock.empty();
2271 if (hasHeaderFields) {
2272 HPack::BitIStream inputStream{ hpackBlock.data(), hpackBlock.data() + hpackBlock.size() };
2273 if (!decoder.decodeHeaderFields(inputStream))
2274 return connectionError(COMPRESSION_ERROR, u"HPACK decompression failed"_s);
2275 } else {
2276 if (firstFrameType == FrameType::PUSH_PROMISE) {
2277 // It could be a PRIORITY sent in HEADERS - already handled by this
2278 // point in handleHEADERS. If it was PUSH_PROMISE (HTTP/2 8.2.1):
2279 // "The header fields in PUSH_PROMISE and any subsequent CONTINUATION
2280 // frames MUST be a valid and complete set of request header fields
2281 // (Section 8.1.2.3) ... If a client receives a PUSH_PROMISE that does
2282 // not include a complete and valid set of header fields or the :method
2283 // pseudo-header field identifies a method that is not safe, it MUST
2284 // respond with a stream error (Section 5.4.2) of type PROTOCOL_ERROR."
2285 if (streamIt != m_streams.cend())
2286 (*streamIt)->streamError(PROTOCOL_ERROR, u"PUSH_PROMISE with incomplete headers"_s);
2287 return;
2288 }
2289
2290 // We got back an empty hpack block. Now let's figure out if there was an error.
2291 constexpr auto hpackBlockHasContent = [](const auto &c) { return c.hpackBlockSize() > 0; };
2292 const bool anyHpackBlock = std::any_of(continuedFrames.cbegin(), continuedFrames.cend(),
2293 hpackBlockHasContent);
2294 if (anyHpackBlock) // There was hpack block data, but returned empty => it overflowed.
2295 return connectionError(FRAME_SIZE_ERROR, u"HEADERS frame too large"_s);
2296 }
2297
2298 if (streamWasResetLocally(streamID) || streamIt == m_streams.cend())
2299 return; // No more processing without a stream from here on.
2300 if (streamIsIgnored(streamID)) {
2301 // Stream was established after GOAWAY cut-off, we ignore it, but we
2302 // have to process things that alter state. That already happened, so we
2303 // stop here.
2304 if (continuedFrames[0].flags().testFlag(Http2::FrameFlag::END_STREAM)) {
2305 if (QHttp2Stream *stream = streamIt.value()) {
2306 stream->setState(QHttp2Stream::State::Closed);
2307 delete stream;
2308 }
2309 }
2310 return;
2311 }
2312
2313 switch (firstFrameType) {
2314 case FrameType::HEADERS:
2315 streamIt.value()->handleHEADERS(continuedFrames[0].flags(), decoder.decodedHeader());
2316 break;
2317 case FrameType::PUSH_PROMISE: {
2318 std::optional<QUrl> promiseKey = HPack::makePromiseKeyUrl(decoder.decodedHeader());
2319 if (!promiseKey)
2320 return; // invalid URL/key !
2321 if (m_promisedStreams.contains(*promiseKey))
2322 return; // already promised!
2323 const auto promiseID = qFromBigEndian<quint32>(continuedFrames[0].dataBegin());
2324 QHttp2Stream *stream = m_streams.value(promiseID);
2325 stream->transitionState(QHttp2Stream::StateTransition::CloseLocal);
2326 stream->handleHEADERS(continuedFrames[0].flags(), decoder.decodedHeader());
2327 emit newPromisedStream(stream); // @future[consider] add promise key as argument?
2328 m_promisedStreams.emplace(*promiseKey, promiseID);
2329 break;
2330 }
2331 default:
2332 break;
2333 }
2334}
2335
2336bool QHttp2Connection::acceptSetting(Http2::Settings identifier, quint32 newValue)
2337{
2338 switch (identifier) {
2339 case Settings::HEADER_TABLE_SIZE_ID: {
2340 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS HEADER_TABLE_SIZE %d", this, newValue);
2341 if (newValue > maxAcceptableTableSize) {
2342 connectionError(PROTOCOL_ERROR, u"SETTINGS invalid table size"_s);
2343 return false;
2344 }
2345 if (!pendingTableSizeUpdates[0] && encoder.dynamicTableCapacity() == newValue) {
2346 qCDebug(qHttp2ConnectionLog,
2347 "[%p] Ignoring SETTINGS HEADER_TABLE_SIZE %d (same as current value)", this,
2348 newValue);
2349 break;
2350 }
2351
2352 if (pendingTableSizeUpdates[0].value_or(std::numeric_limits<quint32>::max()) >= newValue) {
2353 pendingTableSizeUpdates[0] = newValue;
2354 pendingTableSizeUpdates[1].reset(); // 0 is the latest _and_ smallest, so we don't need 1
2355 qCDebug(qHttp2ConnectionLog, "[%p] Pending table size update to %u", this, newValue);
2356 } else {
2357 pendingTableSizeUpdates[1] = newValue; // newValue was larger than 0, so it goes to 1
2358 qCDebug(qHttp2ConnectionLog, "[%p] Pending 2nd table size update to %u, smallest is %u",
2359 this, newValue, *pendingTableSizeUpdates[0]);
2360 }
2361 break;
2362 }
2363 case Settings::INITIAL_WINDOW_SIZE_ID: {
2364 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS INITIAL_WINDOW_SIZE %d", this,
2365 newValue);
2366 // For every active stream - adjust its window
2367 // (and handle possible overflows as errors).
2368 if (newValue > quint32(std::numeric_limits<qint32>::max())) {
2369 connectionError(FLOW_CONTROL_ERROR, u"SETTINGS invalid initial window size"_s);
2370 return false;
2371 }
2372
2373 const qint32 delta = qint32(newValue) - streamInitialSendWindowSize;
2374 streamInitialSendWindowSize = qint32(newValue);
2375
2376 qCDebug(qHttp2ConnectionLog, "[%p] Adjusting initial window size for %zu streams by %d",
2377 this, size_t(m_streams.size()), delta);
2378 for (const QPointer<QHttp2Stream> &stream : std::as_const(m_streams)) {
2379 if (!stream)
2380 continue;
2381 qint32 sum = 0;
2382 // RFC 9113, 6.9.2: a SETTINGS_INITIAL_WINDOW_SIZE change that pushes any
2383 // flow-control window past 2^31-1 is a connection error of type FLOW_CONTROL_ERROR.
2384 if (qAddOverflow(stream->m_sendWindow, delta, &sum)) {
2385 connectionError(FLOW_CONTROL_ERROR,
2386 u"SETTINGS_INITIAL_WINDOW_SIZE overflowed a flow-control window"_s);
2387 return false;
2388 }
2389 stream->m_sendWindow = sum;
2390 if (delta > 0 && stream->isUploadingDATA() && !stream->isUploadBlocked()) {
2391 QMetaObject::invokeMethod(stream, &QHttp2Stream::maybeResumeUpload,
2392 Qt::QueuedConnection);
2393 }
2394 }
2395 break;
2396 }
2397 case Settings::MAX_CONCURRENT_STREAMS_ID: {
2398 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS MAX_CONCURRENT_STREAMS %d", this,
2399 newValue);
2400 m_peerMaxConcurrentStreams = newValue;
2401 break;
2402 }
2403 case Settings::MAX_FRAME_SIZE_ID: {
2404 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS MAX_FRAME_SIZE %d", this, newValue);
2405 if (newValue < Http2::minPayloadLimit || newValue > Http2::maxPayloadSize) {
2406 connectionError(PROTOCOL_ERROR, u"SETTINGS max frame size is out of range"_s);
2407 return false;
2408 }
2409 maxFrameSize = newValue;
2410 break;
2411 }
2412 case Settings::MAX_HEADER_LIST_SIZE_ID: {
2413 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS MAX_HEADER_LIST_SIZE %d", this,
2414 newValue);
2415 // We just remember this value, it can later
2416 // prevent us from sending any request (and this
2417 // will end up in request/reply error).
2418 m_maxHeaderListSize = newValue;
2419 break;
2420 }
2421 case Http2::Settings::ENABLE_PUSH_ID:
2422 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS ENABLE_PUSH %d", this, newValue);
2423 if (newValue != 0 && newValue != 1) {
2424 connectionError(PROTOCOL_ERROR, u"SETTINGS peer sent illegal value for ENABLE_PUSH"_s);
2425 return false;
2426 }
2427 if (m_connectionType == Type::Client) {
2428 if (newValue == 1) {
2429 connectionError(PROTOCOL_ERROR, u"SETTINGS server sent ENABLE_PUSH=1"_s);
2430 return false;
2431 }
2432 } else { // server-side
2433 pushPromiseEnabled = newValue;
2434 break;
2435 }
2436 }
2437
2438 return true;
2439}
2440
2441QT_END_NAMESPACE
2442
2443#include "moc_qhttp2connection_p.cpp"
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)