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
289
290void QHttp2Stream::streamError(Http2::Http2Error errorCode,
291 QLatin1StringView message)
292{
293 qCDebug(qHttp2ConnectionLog, "[%p] stream %u finished with error: %ls (error code: %u)",
294 getConnection(), m_streamID, qUtf16Printable(message), errorCode);
295
296 sendRST_STREAM(errorCode);
297 emit errorOccurred(errorCode, message);
298}
299
300/*!
301 Sends a RST_STREAM frame with the given \a errorCode.
302 This closes the stream for both sides, any further frames will be dropped.
303
304 Returns \c false if the stream is closed or idle, also if it fails to send
305 the RST_STREAM frame. Otherwise, returns \c true.
306*/
307bool QHttp2Stream::sendRST_STREAM(Http2::Http2Error errorCode)
308{
309 if (m_state == State::Closed || m_state == State::Idle) {
310 qCDebug(qHttp2ConnectionLog, "[%p] could not send RST_STREAM on %s stream %u",
311 getConnection(), QDebug::toBytes(m_state).constData(), m_streamID);
312 return false;
313 }
314 // Never respond to a RST_STREAM with a RST_STREAM or looping might occur.
315 if (m_RST_STREAM_received.has_value())
316 return false;
317
318 getConnection()->registerStreamAsResetLocally(streamID());
319
320 m_RST_STREAM_sent = errorCode;
321 qCDebug(qHttp2ConnectionLog, "[%p] sending RST_STREAM on stream %u, code: %u", getConnection(),
322 m_streamID, errorCode);
323 transitionState(StateTransition::RST);
324
325 QHttp2Connection *connection = getConnection();
326 FrameWriter &frameWriter = connection->frameWriter;
327 frameWriter.start(FrameType::RST_STREAM, FrameFlag::EMPTY, m_streamID);
328 frameWriter.append(quint32(errorCode));
329 return frameWriter.write(*connection->getSocket());
330}
331
332/*!
333 Sends a DATA frame with the bytes obtained from \a payload.
334
335 This function will send as many DATA frames as needed to send all the data
336 from \a payload. If \a endStream is \c true, the END_STREAM flag will be
337 set.
338
339 Returns \c{true} if we were able to \e{start} writing to the socket,
340 false otherwise.
341 Note that even though we started writing, the socket may error out before
342 this function returns. Call state() for the new status.
343*/
344bool QHttp2Stream::sendDATA(const QByteArray &payload, bool endStream)
345{
346 Q_ASSERT(!m_uploadByteDevice);
347 if (m_state != State::Open && m_state != State::HalfClosedRemote)
348 return false;
349
350 auto *byteDevice = QNonContiguousByteDeviceFactory::create(payload);
351 m_owningByteDevice = true;
352 byteDevice->setParent(this);
353 return sendDATA(byteDevice, endStream);
354}
355
356/*!
357 Sends a DATA frame with the bytes obtained from \a device.
358
359 This function will send as many DATA frames as needed to send all the data
360 from \a device. If \a endStream is \c true, the END_STREAM flag will be set.
361
362 \a device must stay alive for the duration of the upload.
363 A way of doing this is to heap-allocate the \a device and parent it to the
364 QHttp2Stream.
365
366 Returns \c{true} if we were able to \e{start} writing to the socket,
367 false otherwise.
368 Note that even though we started writing, the socket may error out before
369 this function returns. Call state() for the new status.
370*/
371bool QHttp2Stream::sendDATA(QIODevice *device, bool endStream)
372{
373 Q_ASSERT(!m_uploadDevice);
374 Q_ASSERT(!m_uploadByteDevice);
375 Q_ASSERT(device);
376 if (m_state != State::Open && m_state != State::HalfClosedRemote) {
377 qCWarning(qHttp2ConnectionLog, "[%p] attempt to sendDATA on closed stream %u, "
378 "of device: %p.",
379 getConnection(), m_streamID, device);
380 return false;
381 }
382
383 qCDebug(qHttp2ConnectionLog, "[%p] starting sendDATA on stream %u, of device: %p",
384 getConnection(), m_streamID, device);
385 auto *byteDevice = QNonContiguousByteDeviceFactory::create(device);
386 m_owningByteDevice = true;
387 byteDevice->setParent(this);
388 m_uploadDevice = device;
389 return sendDATA(byteDevice, endStream);
390}
391
392/*!
393 Sends a DATA frame with the bytes obtained from \a device.
394
395 This function will send as many DATA frames as needed to send all the data
396 from \a device. If \a endStream is \c true, the END_STREAM flag will be set.
397
398 \a device must stay alive for the duration of the upload.
399 A way of doing this is to heap-allocate the \a device and parent it to the
400 QHttp2Stream.
401
402 Returns \c{true} if we were able to \e{start} writing to the socket,
403 false otherwise.
404 Note that even though we started writing, the socket may error out before
405 this function returns. Call state() for the new status.
406*/
407bool QHttp2Stream::sendDATA(QNonContiguousByteDevice *device, bool endStream)
408{
409 Q_ASSERT(!m_uploadByteDevice);
410 Q_ASSERT(device);
411 if (m_state != State::Open && m_state != State::HalfClosedRemote) {
412 qCWarning(qHttp2ConnectionLog, "[%p] attempt to sendDATA on closed stream %u, "
413 "of device: %p.",
414 getConnection(), m_streamID, device);
415 return false;
416 }
417
418 qCDebug(qHttp2ConnectionLog, "[%p] starting sendDATA on stream %u, of device: %p",
419 getConnection(), m_streamID, device);
420 m_uploadByteDevice = device;
421 m_endStreamAfterDATA = endStream;
422 connect(m_uploadByteDevice, &QNonContiguousByteDevice::readyRead, this,
423 &QHttp2Stream::maybeResumeUpload);
424 connect(m_uploadByteDevice, &QObject::destroyed, this, &QHttp2Stream::uploadDeviceDestroyed);
425
426 internalSendDATA();
427 // There is no early-out in internalSendDATA so if we reach this spot we
428 // have at least started to send something, even if it errors out.
429 return true;
430}
431
432void QHttp2Stream::internalSendDATA()
433{
434 Q_ASSERT(m_uploadByteDevice);
435 QHttp2Connection *connection = getConnection();
436 Q_ASSERT(connection->maxFrameSize > frameHeaderSize);
437 QIODevice *socket = connection->getSocket();
438
439 qCDebug(qHttp2ConnectionLog,
440 "[%p] stream %u, about to write to socket, current session window size: %d, stream "
441 "window size: %d, bytes available: %lld",
442 connection, m_streamID, connection->sessionSendWindowSize, m_sendWindow,
443 m_uploadByteDevice->size() - m_uploadByteDevice->pos());
444
445 qint32 remainingWindowSize = std::min<qint32>(connection->sessionSendWindowSize, m_sendWindow);
446 FrameWriter &frameWriter = connection->frameWriter;
447 qint64 totalBytesWritten = 0;
448 const auto deviceCanRead = [this, connection] {
449 // We take advantage of knowing the internals of one of the devices used.
450 // It will request X bytes to move over to the http thread if there's
451 // not enough left, so we give it a large size. It will anyway return
452 // the size it can actually provide.
453 const qint64 requestSize = connection->maxFrameSize * 10ll;
454 qint64 tmp = 0;
455 return m_uploadByteDevice->readPointer(requestSize, tmp) != nullptr && tmp > 0;
456 };
457
458 bool sentEND_STREAM = false;
459 while (remainingWindowSize && deviceCanRead()) {
460 quint32 bytesWritten = 0;
461 qint32 remainingBytesInFrame = qint32(connection->maxFrameSize);
462 frameWriter.start(FrameType::DATA, FrameFlag::EMPTY, streamID());
463
464 while (remainingWindowSize && deviceCanRead() && remainingBytesInFrame) {
465 const qint32 maxToWrite = std::min(remainingWindowSize, remainingBytesInFrame);
466
467 qint64 outBytesAvail = 0;
468 const char *readPointer = m_uploadByteDevice->readPointer(maxToWrite, outBytesAvail);
469 if (!readPointer || outBytesAvail <= 0) {
470 qCDebug(qHttp2ConnectionLog,
471 "[%p] stream %u, cannot write data, device (%p) has %lld bytes available",
472 connection, m_streamID, m_uploadByteDevice, outBytesAvail);
473 break;
474 }
475 const qint32 bytesToWrite = qint32(std::min<qint64>(maxToWrite, outBytesAvail));
476 frameWriter.append(QByteArrayView(readPointer, bytesToWrite));
477 m_uploadByteDevice->advanceReadPointer(bytesToWrite);
478
479 bytesWritten += bytesToWrite;
480
481 m_sendWindow -= bytesToWrite;
482 Q_ASSERT(m_sendWindow >= 0);
483 connection->sessionSendWindowSize -= bytesToWrite;
484 Q_ASSERT(connection->sessionSendWindowSize >= 0);
485 remainingBytesInFrame -= bytesToWrite;
486 Q_ASSERT(remainingBytesInFrame >= 0);
487 remainingWindowSize -= bytesToWrite;
488 Q_ASSERT(remainingWindowSize >= 0);
489 }
490
491 qCDebug(qHttp2ConnectionLog, "[%p] stream %u, writing %u bytes to socket", connection,
492 m_streamID, bytesWritten);
493 if (!deviceCanRead() && m_uploadByteDevice->atEnd() && m_endStreamAfterDATA) {
494 sentEND_STREAM = true;
495 frameWriter.addFlag(FrameFlag::END_STREAM);
496 }
497 if (!frameWriter.write(*socket)) {
498 qCDebug(qHttp2ConnectionLog, "[%p] stream %u, failed to write to socket", connection,
499 m_streamID);
500 return finishWithError(INTERNAL_ERROR, "failed to write to socket"_L1);
501 }
502
503 totalBytesWritten += bytesWritten;
504 }
505
506 qCDebug(qHttp2ConnectionLog,
507 "[%p] stream %u, wrote %lld bytes total, if the device is not exhausted, we'll write "
508 "more later. Remaining window size: %d",
509 connection, m_streamID, totalBytesWritten, remainingWindowSize);
510
511 emit bytesWritten(totalBytesWritten);
512 if (sentEND_STREAM || (!deviceCanRead() && m_uploadByteDevice->atEnd())) {
513 qCDebug(qHttp2ConnectionLog,
514 "[%p] stream %u, exhausted device %p, sent END_STREAM? %d, %ssending end stream "
515 "after DATA",
516 connection, m_streamID, m_uploadByteDevice, sentEND_STREAM,
517 !sentEND_STREAM && m_endStreamAfterDATA ? "" : "not ");
518 if (!sentEND_STREAM && m_endStreamAfterDATA) {
519 // We need to send an empty DATA frame with END_STREAM since we
520 // have exhausted the device, but we haven't sent END_STREAM yet.
521 // This can happen if we got a final readyRead to signify no more
522 // data available, but we hadn't sent the END_STREAM flag yet.
523 frameWriter.start(FrameType::DATA, FrameFlag::END_STREAM, streamID());
524 frameWriter.write(*socket);
525 }
526 finishSendDATA();
527 } else if (isUploadBlocked()) {
528 qCDebug(qHttp2ConnectionLog, "[%p] stream %u, upload blocked", connection, m_streamID);
529 emit uploadBlocked();
530 }
531}
532
533void QHttp2Stream::finishSendDATA()
534{
535 if (m_endStreamAfterDATA)
536 transitionState(StateTransition::CloseLocal);
537
538 disconnect(m_uploadByteDevice, nullptr, this, nullptr);
539 m_uploadDevice = nullptr;
540 if (m_owningByteDevice) {
541 m_owningByteDevice = false;
542 delete m_uploadByteDevice;
543 }
544 m_uploadByteDevice = nullptr;
545 emit uploadFinished();
546}
547
548void QHttp2Stream::maybeResumeUpload()
549{
550 qCDebug(qHttp2ConnectionLog,
551 "[%p] stream %u, maybeResumeUpload. Upload device: %p, bytes available: %lld, blocked? "
552 "%d",
553 getConnection(), m_streamID, m_uploadByteDevice,
554 !m_uploadByteDevice ? 0 : m_uploadByteDevice->size() - m_uploadByteDevice->pos(),
555 isUploadBlocked());
556 if (isUploadingDATA() && !isUploadBlocked())
557 internalSendDATA();
558 else
559 getConnection()->m_blockedStreams.insert(streamID());
560}
561
562/*!
563 Returns \c true if the stream is currently unable to send more data because
564 the remote peer's receive window is full.
565*/
566bool QHttp2Stream::isUploadBlocked() const noexcept
567{
568 constexpr auto MinFrameSize = Http2::frameHeaderSize + 1; // 1 byte payload
569 return isUploadingDATA()
570 && (m_sendWindow <= MinFrameSize
571 || getConnection()->sessionSendWindowSize <= MinFrameSize);
572}
573
574void QHttp2Stream::uploadDeviceReadChannelFinished()
575{
576 maybeResumeUpload();
577}
578
579/*!
580 Sends a HEADERS frame with the given \a headers and \a priority.
581 If \a endStream is \c true, the END_STREAM flag will be set, and the stream
582 will be closed for future writes.
583 If the headers are too large, or the stream is not in the correct state,
584 this function will return \c false. Otherwise, it will return \c true.
585*/
586bool QHttp2Stream::sendHEADERS(const HPack::HttpHeader &headers, bool endStream, quint8 priority)
587{
588 using namespace HPack;
589 if (auto hs = header_size(headers);
590 !hs.first || hs.second > getConnection()->maxHeaderListSize()) {
591 return false;
592 }
593
594 transitionState(StateTransition::Open);
595
596 Q_ASSERT(m_state == State::Open || m_state == State::HalfClosedRemote);
597
598 QHttp2Connection *connection = getConnection();
599
600 qCDebug(qHttp2ConnectionLog, "[%p] stream %u, sending HEADERS frame with %u entries",
601 connection, streamID(), uint(headers.size()));
602
603 QIODevice *socket = connection->getSocket();
604 FrameWriter &frameWriter = connection->frameWriter;
605
606 frameWriter.start(FrameType::HEADERS, FrameFlag::PRIORITY | FrameFlag::END_HEADERS, streamID());
607 if (endStream)
608 frameWriter.addFlag(FrameFlag::END_STREAM);
609
610 frameWriter.append(quint32()); // No stream dependency in Qt.
611 frameWriter.append(priority);
612
613 // Compress in-place:
614 BitOStream outputStream(frameWriter.outboundFrame().buffer);
615
616 // Possibly perform and notify of dynamic table size update:
617 for (auto &maybePendingTableSizeUpdate : connection->pendingTableSizeUpdates) {
618 if (!maybePendingTableSizeUpdate)
619 break; // They are ordered, so if the first one is null, the other one is too.
620 qCDebug(qHttp2ConnectionLog, "[%p] stream %u, sending dynamic table size update of size %u",
621 connection, streamID(), *maybePendingTableSizeUpdate);
622 connection->encoder.setMaxDynamicTableSize(*maybePendingTableSizeUpdate);
623 connection->encoder.encodeSizeUpdate(outputStream, *maybePendingTableSizeUpdate);
624 maybePendingTableSizeUpdate.reset();
625 }
626
627 if (connection->m_connectionType == QHttp2Connection::Type::Client) {
628 if (!connection->encoder.encodeRequest(outputStream, headers))
629 return false;
630 } else {
631 if (!connection->encoder.encodeResponse(outputStream, headers))
632 return false;
633 }
634
635 bool result = frameWriter.writeHEADERS(*socket, connection->maxFrameSize);
636 if (endStream)
637 transitionState(StateTransition::CloseLocal);
638
639 return result;
640}
641
642/*!
643 Sends a WINDOW_UPDATE frame with the given \a delta.
644 This increases our receive window size for this stream, allowing the remote
645 peer to send more data.
646*/
647void QHttp2Stream::sendWINDOW_UPDATE(quint32 delta)
648{
649 QHttp2Connection *connection = getConnection();
650 m_recvWindow += qint32(delta);
651 connection->sendWINDOW_UPDATE(streamID(), delta);
652}
653
654void QHttp2Stream::uploadDeviceDestroyed()
655{
656 if (isUploadingDATA()) {
657 // We're in the middle of sending DATA frames, we need to abort
658 // the stream.
659 streamError(CANCEL, QLatin1String("Upload device destroyed while uploading"));
660 emit uploadDeviceError("Upload device destroyed while uploading"_L1);
661 }
662 m_uploadDevice = nullptr;
663}
664
665void QHttp2Stream::setState(State newState)
666{
667 if (m_state == newState)
668 return;
669 qCDebug(qHttp2ConnectionLog, "[%p] stream %u, state changed from %d to %d", getConnection(),
670 streamID(), int(m_state), int(newState));
671 m_state = newState;
672 emit stateChanged(newState);
673 if (m_state == State::Closed)
674 getConnection()->maybeCloseOnGoingAway();
675}
676
677// Changes the state as appropriate given the current state and the transition.
678// Always call this before emitting any signals since the recipient might rely
679// on the new state!
680void QHttp2Stream::transitionState(StateTransition transition)
681{
682 switch (m_state) {
683 case State::Idle:
684 if (transition == StateTransition::Open)
685 setState(State::Open);
686 else
687 Q_UNREACHABLE(); // We should transition to Open before ever getting here
688 break;
689 case State::Open:
690 switch (transition) {
691 case StateTransition::CloseLocal:
692 setState(State::HalfClosedLocal);
693 break;
694 case StateTransition::CloseRemote:
695 setState(State::HalfClosedRemote);
696 break;
697 case StateTransition::RST:
698 setState(State::Closed);
699 break;
700 case StateTransition::Open: // no-op
701 break;
702 }
703 break;
704 case State::HalfClosedLocal:
705 if (transition == StateTransition::CloseRemote || transition == StateTransition::RST)
706 setState(State::Closed);
707 break;
708 case State::HalfClosedRemote:
709 if (transition == StateTransition::CloseLocal || transition == StateTransition::RST)
710 setState(State::Closed);
711 break;
712 case State::ReservedRemote:
713 if (transition == StateTransition::RST) {
714 setState(State::Closed);
715 } else if (transition == StateTransition::CloseLocal) { // Receiving HEADER closes local
716 setState(State::HalfClosedLocal);
717 }
718 break;
719 case State::Closed:
720 break;
721 }
722}
723
724void QHttp2Stream::handleDATA(const Frame &inboundFrame)
725{
726 QHttp2Connection *connection = getConnection();
727
728 qCDebug(qHttp2ConnectionLog,
729 "[%p] stream %u, received DATA frame with payload of %u bytes, closing stream? %s",
730 connection, m_streamID, inboundFrame.payloadSize(),
731 inboundFrame.flags().testFlag(Http2::FrameFlag::END_STREAM) ? "yes" : "no");
732
733 // RFC 9113, 6.1: If a DATA frame is received whose stream is not in the "open" or "half-closed
734 // (local)" state, the recipient MUST respond with a stream error (Section 5.4.2) of type
735 // STREAM_CLOSED;
736 // checked in QHttp2Connection
737 Q_ASSERT(state() != State::HalfClosedRemote && state() != State::Closed);
738
739 if (qint32(inboundFrame.payloadSize()) > m_recvWindow) {
740 qCDebug(qHttp2ConnectionLog,
741 "[%p] stream %u, received DATA frame with payload size %u, "
742 "but recvWindow is %d, sending FLOW_CONTROL_ERROR",
743 connection, m_streamID, inboundFrame.payloadSize(), m_recvWindow);
744 return streamError(FLOW_CONTROL_ERROR, QLatin1String("data bigger than window size"));
745 }
746 // RFC 9113, 6.1: The total number of padding octets is determined by the value of the Pad
747 // Length field. If the length of the padding is the length of the frame payload or greater,
748 // the recipient MUST treat this as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
749 // checked in Framereader
750 Q_ASSERT(inboundFrame.buffer.size() >= frameHeaderSize);
751 Q_ASSERT(inboundFrame.payloadSize() + frameHeaderSize == inboundFrame.buffer.size());
752
753 m_recvWindow -= qint32(inboundFrame.payloadSize());
754 const bool endStream = inboundFrame.flags().testFlag(FrameFlag::END_STREAM);
755 const bool ignoreData = connection->streamIsIgnored(m_streamID);
756 // Uncompress data if needed and append it ...
757 if ((inboundFrame.dataSize() > 0 || endStream) && !ignoreData) {
758 QByteArray fragment(reinterpret_cast<const char *>(inboundFrame.dataBegin()),
759 inboundFrame.dataSize());
760 if (endStream)
761 transitionState(StateTransition::CloseRemote);
762 const auto shouldBuffer = m_configuration.useDownloadBuffer && !fragment.isEmpty();
763 if (shouldBuffer) {
764 // Only non-empty fragments get appended!
765 m_downloadBuffer.append(std::move(fragment));
766 emit dataReceived(m_downloadBuffer.last(), endStream);
767 } else {
768 emit dataReceived(fragment, endStream);
769 }
770 }
771
772 if (!endStream && m_recvWindow < connection->streamInitialReceiveWindowSize / 2) {
773 // @future[consider]: emit signal instead
774 sendWINDOW_UPDATE(quint32(connection->streamInitialReceiveWindowSize - m_recvWindow));
775 }
776}
777
778void QHttp2Stream::handleHEADERS(Http2::FrameFlags frameFlags, const HPack::HttpHeader &headers)
779{
780 if (m_state == State::Idle)
781 transitionState(StateTransition::Open);
782 const bool endStream = frameFlags.testFlag(FrameFlag::END_STREAM);
783 if (endStream)
784 transitionState(StateTransition::CloseRemote);
785 if (!headers.empty() && m_configuration.useHeaderBuffer) {
786 m_headers.insert(m_headers.end(), headers.begin(), headers.end());
787 emit headersUpdated();
788 }
789 emit headersReceived(headers, endStream);
790}
791
792void QHttp2Stream::handleRST_STREAM(const Frame &inboundFrame)
793{
794 if (m_state == State::Closed) // The stream is already closed, we're not sending anything anyway
795 return;
796
797 transitionState(StateTransition::RST);
798 m_RST_STREAM_received = qFromBigEndian<quint32>(inboundFrame.dataBegin());
799 if (isUploadingDATA()) {
800 disconnect(m_uploadByteDevice, nullptr, this, nullptr);
801 m_uploadDevice = nullptr;
802 m_uploadByteDevice = nullptr;
803 }
804 finishWithError(Http2Error(*m_RST_STREAM_received));
805}
806
807void QHttp2Stream::handleWINDOW_UPDATE(const Frame &inboundFrame)
808{
809 const quint32 delta = qFromBigEndian<quint32>(inboundFrame.dataBegin());
810 const bool valid = delta && delta <= quint32(std::numeric_limits<qint32>::max());
811 if (!valid) {
812 // RFC 9113, 6.9.1: a flow-control window increment of 0 is a stream error of
813 // type PROTOCOL_ERROR.
814 qCDebug(qHttp2ConnectionLog,
815 "[%p] stream %u, received WINDOW_UPDATE frame with invalid delta %u, sending "
816 "PROTOCOL_ERROR",
817 getConnection(), m_streamID, delta);
818 return streamError(PROTOCOL_ERROR, "invalid WINDOW_UPDATE delta"_L1);
819 }
820 qint32 sum = 0;
821 if (qAddOverflow(m_sendWindow, qint32(delta), &sum)) {
822 // RFC 9113, 6.9.1: a WINDOW_UPDATE that pushes the window past 2^31-1 is a stream
823 // error of type FLOW_CONTROL_ERROR (the sender sends RST_STREAM).
824 qCDebug(qHttp2ConnectionLog,
825 "[%p] stream %u, WINDOW_UPDATE delta %u overflows the flow-control window, "
826 "sending FLOW_CONTROL_ERROR",
827 getConnection(), m_streamID, delta);
828 return streamError(FLOW_CONTROL_ERROR, "WINDOW_UPDATE exceeds maximum window"_L1);
829 }
830 m_sendWindow = sum;
831 // Stream may have been unblocked, so maybe try to write again
832 if (isUploadingDATA())
833 maybeResumeUpload();
834}
835
836/*!
837 \class QHttp2Connection
838 \inmodule QtNetwork
839 \internal
840
841 The QHttp2Connection class represents a HTTP/2 connection.
842 It can only be created through the static functions
843 createDirectConnection(), createUpgradedConnection(),
844 and createDirectServerConnection().
845
846 createDirectServerConnection() is used for server-side connections, and has
847 certain limitations that a client does not.
848
849 As a client you can create a QHttp2Stream with createStream().
850
851 \sa QHttp2Stream
852*/
853
854/*!
855 \fn void QHttp2Connection::newIncomingStream(QHttp2Stream *stream)
856
857 This signal is emitted when a new \a stream is received from the remote
858 peer.
859*/
860
861/*!
862 \fn void QHttp2Connection::newPromisedStream(QHttp2Stream *stream)
863
864 This signal is emitted when the remote peer has promised a new \a stream.
865*/
866
867/*!
868 \fn void QHttp2Connection::errorReceived()
869
870 This signal is emitted when the connection has received an error.
871*/
872
873/*!
874 \fn void QHttp2Connection::connectionClosed()
875
876 This signal is emitted when the connection has been closed.
877*/
878
879/*!
880 \fn void QHttp2Connection::settingsFrameReceived()
881
882 This signal is emitted when the connection has received a SETTINGS frame.
883*/
884
885/*!
886 \fn void QHttp2Connection::errorOccurred(Http2::Http2Error errorCode, const QString &errorString)
887
888 This signal is emitted when the connection has encountered an error. The
889 \a errorCode parameter is the HTTP/2 error code, and the \a errorString
890 parameter is a human-readable description of the error.
891*/
892
893/*!
894 \fn void QHttp2Connection::receivedGOAWAY(Http2::Http2Error errorCode, quint32 lastStreamID)
895
896 This signal is emitted when the connection has received a GOAWAY frame. The
897 \a errorCode parameter is the HTTP/2 error code, and the \a lastStreamID
898 parameter is the last stream ID that the remote peer will process.
899
900 Any streams of a higher stream ID created by us will be ignored or reset.
901*/
902
903/*!
904 Create a new HTTP2 connection given a \a config and a \a socket.
905 This function assumes that the Upgrade headers etc. in http/1 have already
906 been sent and that the connection is already upgraded to http/2.
907
908 The object returned will be a child to the \a socket, or null on failure.
909*/
910QHttp2Connection *QHttp2Connection::createUpgradedConnection(QIODevice *socket,
911 const QHttp2Configuration &config)
912{
913 Q_ASSERT(socket);
914
915 auto connection = std::unique_ptr<QHttp2Connection>(new QHttp2Connection(socket));
916 connection->setH2Configuration(config);
917 connection->m_connectionType = QHttp2Connection::Type::Client;
918 connection->m_upgradedConnection = true;
919 // HTTP2 connection is already established and request was sent, so stream 1
920 // is already 'active' and is closed for any further outgoing data.
921 QHttp2Stream *stream = connection->createLocalStreamInternal().unwrap();
922 Q_ASSERT(stream->streamID() == 1);
923 stream->setState(QHttp2Stream::State::HalfClosedLocal);
924
925 if (!connection->m_prefaceSent) // Preface is sent as part of initial stream-creation.
926 return nullptr;
927
928 return connection.release();
929}
930
931/*!
932 Create a new HTTP2 connection given a \a config and a \a socket.
933 This function will immediately send the client preface.
934
935 The object returned will be a child to the \a socket, or null on failure.
936*/
937QHttp2Connection *QHttp2Connection::createDirectConnection(QIODevice *socket,
938 const QHttp2Configuration &config)
939{
940 auto connection = std::unique_ptr<QHttp2Connection>(new QHttp2Connection(socket));
941 connection->setH2Configuration(config);
942 connection->m_connectionType = QHttp2Connection::Type::Client;
943
944 return connection.release();
945}
946
947/*!
948 Create a new HTTP2 connection given a \a config and a \a socket.
949
950 The object returned will be a child to the \a socket, or null on failure.
951*/
952QHttp2Connection *QHttp2Connection::createDirectServerConnection(QIODevice *socket,
953 const QHttp2Configuration &config)
954{
955 auto connection = std::unique_ptr<QHttp2Connection>(new QHttp2Connection(socket));
956 connection->setH2Configuration(config);
957 connection->m_connectionType = QHttp2Connection::Type::Server;
958
959 connection->m_nextStreamID = 2; // server-initiated streams must be even
960
961 connection->m_waitingForClientPreface = true;
962
963 return connection.release();
964}
965
966/*!
967 \fn QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError> QHttp2Connection::createStream()
968
969 Creates a stream on this connection, using the default QHttp2Stream::Configuration.
970
971//! [createStream]
972 Automatically picks the next available stream ID and returns a pointer to
973 the new stream, if possible. Otherwise returns an error.
974
975 \sa QHttp2Connection::CreateStreamError, QHttp2Stream
976//! [createStream]
977 \sa createStream(QHttp2Stream::Configuration)
978*/
979
980/*!
981 Creates a stream with \a configuration on this connection.
982
983 \include qhttp2connection.cpp createStream
984*/
985QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError>
986QHttp2Connection::createStream(QHttp2Stream::Configuration configuration)
987{
988 Q_ASSERT(m_connectionType == Type::Client); // This overload is just for clients
989 if (m_nextStreamID > lastValidStreamID)
990 return { QHttp2Connection::CreateStreamError::StreamIdsExhausted };
991 return createLocalStreamInternal(configuration);
992}
993
994QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError>
995QHttp2Connection::createLocalStreamInternal(QHttp2Stream::Configuration conf)
996{
997 if (m_goingAway)
998 return { QHttp2Connection::CreateStreamError::ReceivedGOAWAY };
999 const quint32 streamID = m_nextStreamID;
1000 if (size_t(m_peerMaxConcurrentStreams) <= size_t(numActiveLocalStreams()))
1001 return { QHttp2Connection::CreateStreamError::MaxConcurrentStreamsReached };
1002
1003 if (QHttp2Stream *ptr = createStreamInternal_impl(streamID, conf)) {
1004 m_nextStreamID += 2;
1005 return {ptr};
1006 }
1007 // Connection could be broken, we could've ran out of memory, we don't know
1008 return { QHttp2Connection::CreateStreamError::UnknownError };
1009}
1010
1011QHttp2Stream *QHttp2Connection::createStreamInternal_impl(quint32 streamID,
1012 QHttp2Stream::Configuration conf)
1013{
1014 Q_ASSERT(streamID > m_lastIncomingStreamID || streamID >= m_nextStreamID);
1015
1016 if (m_connectionType == Type::Client && !m_prefaceSent && !sendClientPreface()) {
1017 qCWarning(qHttp2ConnectionLog, "[%p] Failed to send client preface", this);
1018 return nullptr;
1019 }
1020
1021 auto result = m_streams.tryEmplace(streamID, nullptr);
1022 if (!result.inserted)
1023 return nullptr;
1024 QPointer<QHttp2Stream> &stream = result.iterator.value();
1025 stream = new QHttp2Stream(this, streamID, conf);
1026 stream->m_recvWindow = streamInitialReceiveWindowSize;
1027 stream->m_sendWindow = streamInitialSendWindowSize;
1028
1029 connect(stream, &QHttp2Stream::uploadBlocked, this, [this, stream] {
1030 m_blockedStreams.insert(stream->streamID());
1031 });
1032 *result.iterator = stream;
1033 return *result.iterator;
1034}
1035
1036qsizetype QHttp2Connection::numActiveStreamsImpl(quint32 mask) const noexcept
1037{
1038 const auto shouldCount = [mask](const QPointer<QHttp2Stream> &stream) -> bool {
1039 return stream && (stream->streamID() & 1) == mask && stream->isActive();
1040 };
1041 return std::count_if(m_streams.cbegin(), m_streams.cend(), shouldCount);
1042}
1043
1044/*!
1045 \internal
1046 The number of streams the remote peer has started that are still active.
1047*/
1048qsizetype QHttp2Connection::numActiveRemoteStreams() const noexcept
1049{
1050 const quint32 RemoteMask = m_connectionType == Type::Client ? 0 : 1;
1051 return numActiveStreamsImpl(RemoteMask);
1052}
1053
1054/*!
1055 \internal
1056 The number of streams we have started that are still active.
1057*/
1058qsizetype QHttp2Connection::numActiveLocalStreams() const noexcept
1059{
1060 const quint32 LocalMask = m_connectionType == Type::Client ? 1 : 0;
1061 return numActiveStreamsImpl(LocalMask);
1062}
1063
1064/*!
1065 Return a pointer to a stream with the given \a streamID, or null if no such
1066 stream exists or it was deleted.
1067*/
1068QHttp2Stream *QHttp2Connection::getStream(quint32 streamID) const
1069{
1070 return m_streams.value(streamID, nullptr).get();
1071}
1072
1073/*!
1074 Initiates connection shutdown. When \a errorCode is \c{NO_ERROR}, graceful
1075 shutdown is initiated, allowing existing streams to complete. Otherwise the
1076 connection is closed immediately with an error.
1077*/
1078void QHttp2Connection::close(Http2::Http2Error errorCode)
1079{
1080 if (m_connectionAborted)
1081 return;
1082
1083 if (errorCode == Http2::HTTP2_NO_ERROR) {
1084 if (m_connectionType == Type::Server)
1085 sendInitialServerGracefulShutdownGoaway();
1086 else
1087 sendClientGracefulShutdownGoaway();
1088 } else {
1089 // RFC 9113, 5.4.1: After sending the GOAWAY frame for an error
1090 // condition, the endpoint MUST close the TCP connection
1091 connectionError(errorCode, "Connection closed with error", false);
1092 }
1093}
1094
1095/*!
1096 \fn QHttp2Stream *QHttp2Connection::promisedStream(const QUrl &streamKey) const
1097
1098 Returns a pointer to the stream that was promised with the given
1099 \a streamKey, if any. Otherwise, returns null.
1100*/
1101
1102/*!
1103 \fn bool QHttp2Connection::isGoingAway() const noexcept
1104
1105 Returns \c true if the connection is in the process of being closed, or
1106 \c false otherwise.
1107*/
1108
1109/*!
1110 \fn quint32 QHttp2Connection::maxConcurrentStreams() const noexcept
1111
1112 Returns the maximum number of concurrent streams we are allowed to have
1113 active at any given time. This is a directional setting, and the remote
1114 peer may have a different value.
1115*/
1116
1117/*!
1118 \fn quint32 QHttp2Connection::maxHeaderListSize() const noexcept
1119
1120 Returns the maximum size of the header which the peer is willing to accept.
1121*/
1122
1123/*!
1124 \fn bool QHttp2Connection::isUpgradedConnection() const noexcept
1125
1126 Returns \c true if this connection was created as a result of an HTTP/1
1127 upgrade to HTTP/2, or \c false otherwise.
1128*/
1129
1130QHttp2Connection::QHttp2Connection(QIODevice *socket) : QObject(socket)
1131{
1132 Q_ASSERT(socket);
1133 Q_ASSERT(socket->isOpen());
1134 Q_ASSERT(socket->openMode() & QIODevice::ReadWrite);
1135 // We don't make any connections directly because this is used in
1136 // in the http2 protocol handler, which is used by
1137 // QHttpNetworkConnectionChannel. Which in turn owns and deals with all the
1138 // socket connections.
1139}
1140
1141QHttp2Connection::~QHttp2Connection()
1142{
1143 // delete streams now so that any calls it might make back to this
1144 // Connection will operate on a valid object.
1145 for (QPointer<QHttp2Stream> &stream : std::exchange(m_streams, {}))
1146 delete stream.get();
1147}
1148
1149bool QHttp2Connection::serverCheckClientPreface()
1150{
1151 if (!m_waitingForClientPreface)
1152 return true;
1153 auto *socket = getSocket();
1154 if (socket->bytesAvailable() < Http2::clientPrefaceLength)
1155 return false;
1156 if (!readClientPreface()) {
1157 socket->close();
1158 emit errorOccurred(Http2Error::PROTOCOL_ERROR, "invalid client preface"_L1);
1159 qCDebug(qHttp2ConnectionLog, "[%p] Invalid client preface", this);
1160 return false;
1161 }
1162 qCDebug(qHttp2ConnectionLog, "[%p] Peer sent valid client preface", this);
1163 m_waitingForClientPreface = false;
1164 if (!sendServerPreface()) {
1165 connectionError(INTERNAL_ERROR, "Failed to send server preface");
1166 return false;
1167 }
1168 return true;
1169}
1170
1171bool QHttp2Connection::sendPing()
1172{
1173 std::array<char, 8> data;
1174
1175 QRandomGenerator gen;
1176 gen.generate(data.begin(), data.end());
1177 return sendPing(data);
1178}
1179
1180bool QHttp2Connection::sendPing(QByteArrayView data)
1181{
1182 frameWriter.start(FrameType::PING, FrameFlag::EMPTY, connectionStreamID);
1183
1184 Q_ASSERT(data.length() == 8);
1185 if (!m_lastPingSignature) {
1186 m_lastPingSignature = data.toByteArray();
1187 } else {
1188 qCWarning(qHttp2ConnectionLog, "[%p] No PING is sent while waiting for the previous PING.", this);
1189 return false;
1190 }
1191
1192 frameWriter.append((uchar*)data.data(), (uchar*)data.end());
1193 frameWriter.write(*getSocket());
1194 return true;
1195}
1196
1197/*!
1198 This function must be called when you have received a readyRead signal
1199 (or equivalent) from the QIODevice. It will read and process any incoming
1200 HTTP/2 frames and emit signals as appropriate.
1201*/
1202void QHttp2Connection::handleReadyRead()
1203{
1204 /* event loop */
1205 if (m_connectionType == Type::Server && !serverCheckClientPreface())
1206 return;
1207
1208 QIODevice *socket = getSocket();
1209
1210 qCDebug(qHttp2ConnectionLog, "[%p] Receiving data, %lld bytes available", this,
1211 socket->bytesAvailable());
1212
1213 using namespace Http2;
1214 if (!m_prefaceSent)
1215 return;
1216
1217 while (!m_connectionAborted) {
1218 const auto result = frameReader.read(*socket);
1219 if (result != FrameStatus::goodFrame)
1220 qCDebug(qHttp2ConnectionLog, "[%p] Tried to read frame, got %d", this, int(result));
1221 switch (result) {
1222 case FrameStatus::incompleteFrame:
1223 return; // No more complete frames available
1224 case FrameStatus::protocolError:
1225 return connectionError(PROTOCOL_ERROR, "invalid frame");
1226 case FrameStatus::sizeError: {
1227 const auto streamID = frameReader.inboundFrame().streamID();
1228 const auto frameType = frameReader.inboundFrame().type();
1229 auto stream = getStream(streamID);
1230 // RFC 9113, 4.2: A frame size error in a frame that could alter the state of the
1231 // entire connection MUST be treated as a connection error (Section 5.4.1); this
1232 // includes any frame carrying a field block (Section 4.3) (that is, HEADERS,
1233 // PUSH_PROMISE, and CONTINUATION), a SETTINGS frame, and any frame with a stream
1234 // identifier of 0.
1235 if (frameType == FrameType::HEADERS ||
1236 frameType == FrameType::SETTINGS ||
1237 frameType == FrameType::PUSH_PROMISE ||
1238 frameType == FrameType::CONTINUATION ||
1239 // never reply RST_STREAM with RST_STREAM
1240 frameType == FrameType::RST_STREAM ||
1241 streamID == connectionStreamID)
1242 return connectionError(FRAME_SIZE_ERROR, "invalid frame size");
1243 // DATA; PRIORITY; WINDOW_UPDATE
1244 if (stream)
1245 return stream->streamError(Http2Error::FRAME_SIZE_ERROR,
1246 QLatin1String("invalid frame size"));
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, "CONTINUATION expected");
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 char *message, bool logAsError)
1347{
1348 Q_ASSERT(message);
1349 if (m_connectionAborted)
1350 return;
1351 m_connectionAborted = true;
1352
1353 if (logAsError) {
1354 qCCritical(qHttp2ConnectionLog, "[%p] Connection error: %s (%d)", this, message,
1355 int(errorCode));
1356 } else {
1357 qCDebug(qHttp2ConnectionLog, "[%p] Closing connection: %s (%d)", this, message,
1358 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 auto messageView = QLatin1StringView(message);
1368
1369 for (QHttp2Stream *stream : std::as_const(m_streams)) {
1370 if (stream && stream->isActive())
1371 stream->finishWithError(errorCode, messageView);
1372 }
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, "DATA on the connection stream");
1628
1629 if (isInvalidStream(streamID))
1630 return connectionError(ENHANCE_YOUR_CALM, "DATA on invalid stream");
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,
1640 QLatin1String("Data on closed stream"));
1641 }
1642 }
1643
1644 if (inboundFrame.payloadSize() > m_config.maxFrameSize()) {
1645 qCDebug(qHttp2ConnectionLog,
1646 "[%p] Received DATA frame with payload size %u, "
1647 "but SETTINGS_MAX_FRAME_SIZE is %u, sending FRAME_SIZE_ERROR",
1648 this, inboundFrame.payloadSize(), m_config.maxFrameSize());
1649 if (stream)
1650 return stream->streamError(Http2Error::FRAME_SIZE_ERROR,
1651 QLatin1String("DATA payload size exceeds SETTINGS_MAX_FRAME_SIZE"));
1652 return connectionError(FRAME_SIZE_ERROR, "DATA payload size exceeds SETTINGS_MAX_FRAME_SIZE");
1653 }
1654
1655 if (qint32(inboundFrame.payloadSize()) > sessionReceiveWindowSize) {
1656 qCDebug(qHttp2ConnectionLog,
1657 "[%p] Received DATA frame with payload size %u, "
1658 "but recvWindow is %d, sending FLOW_CONTROL_ERROR",
1659 this, inboundFrame.payloadSize(), sessionReceiveWindowSize);
1660 return connectionError(FLOW_CONTROL_ERROR, "Flow control error");
1661 }
1662
1663 sessionReceiveWindowSize -= inboundFrame.payloadSize();
1664 m_totalBytesReceivedDATA += inboundFrame.payloadSize();
1665
1666 if (stream)
1667 stream->handleDATA(inboundFrame);
1668
1669
1670 if (inboundFrame.flags().testFlag(FrameFlag::END_STREAM)) {
1671 const bool ignoreData = stream && streamIsIgnored(stream->streamID());
1672 if (!ignoreData) {
1673 emit receivedEND_STREAM(streamID);
1674 } else {
1675 // Stream opened after our GOAWAY cut-off. We would just drop the
1676 // data, but needed to handle it enough to track sizes of streams and
1677 // connection windows. Since we've now taken care of that, we can
1678 // at last close and delete it.
1679 stream->setState(QHttp2Stream::State::Closed);
1680 delete stream;
1681 }
1682 }
1683
1684 if (sessionReceiveWindowSize < maxSessionReceiveWindowSize / 2) {
1685 // @future[consider]: emit signal instead
1686 QMetaObject::invokeMethod(this, &QHttp2Connection::sendWINDOW_UPDATE, Qt::QueuedConnection,
1687 quint32(connectionStreamID),
1688 quint32(maxSessionReceiveWindowSize - sessionReceiveWindowSize));
1689 sessionReceiveWindowSize = maxSessionReceiveWindowSize;
1690 }
1691}
1692
1693void QHttp2Connection::handleHEADERS()
1694{
1695 Q_ASSERT(inboundFrame.type() == FrameType::HEADERS);
1696
1697 const auto streamID = inboundFrame.streamID();
1698 qCDebug(qHttp2ConnectionLog, "[%p] Received HEADERS frame on stream %d, end stream? %s", this,
1699 streamID, inboundFrame.flags().testFlag(Http2::FrameFlag::END_STREAM) ? "yes" : "no");
1700
1701 // RFC 9113, 6.2: If a HEADERS frame is received whose Stream Identifier field is 0x00, the
1702 // recipient MUST respond with a connection error.
1703 if (streamID == connectionStreamID)
1704 return connectionError(PROTOCOL_ERROR, "HEADERS on 0x0 stream");
1705
1706 if (inboundFrame.payloadSize() > m_config.maxFrameSize()) {
1707 qCDebug(qHttp2ConnectionLog,
1708 "[%p] Received HEADERS frame with payload size %u, "
1709 "but SETTINGS_MAX_FRAME_SIZE is %u, sending FRAME_SIZE_ERROR",
1710 this, inboundFrame.payloadSize(), m_config.maxFrameSize());
1711 return connectionError(Http2Error::FRAME_SIZE_ERROR,
1712 "HEADERS payload size exceeds SETTINGS_MAX_FRAME_SIZE");
1713 }
1714
1715 const bool isClient = m_connectionType == Type::Client;
1716 const bool isClientInitiatedStream = !!(streamID & 1);
1717 const bool isRemotelyInitiatedStream = isClient ^ isClientInitiatedStream;
1718
1719 if (isRemotelyInitiatedStream && streamID > m_lastIncomingStreamID) {
1720 bool streamCountIsOk = size_t(m_maxConcurrentStreams) > size_t(numActiveRemoteStreams());
1721 QHttp2Stream *newStream = createStreamInternal_impl(streamID);
1722 Q_ASSERT(newStream);
1723 m_lastIncomingStreamID = streamID;
1724
1725 if (!streamCountIsOk) {
1726 newStream->setState(QHttp2Stream::State::Open);
1727 newStream->streamError(PROTOCOL_ERROR, QLatin1String("Max concurrent streams reached"));
1728
1729 emit incomingStreamErrorOccured(CreateStreamError::MaxConcurrentStreamsReached);
1730 return;
1731 }
1732
1733 qCDebug(qHttp2ConnectionLog, "[%p] New incoming stream %d", this, streamID);
1734 if (!streamIsIgnored(newStream->streamID())) {
1735 emit newIncomingStream(newStream);
1736 } else if (m_goawayGraceTimer.hasExpired()) {
1737 // We gave the peer some time to handle the GOAWAY message, but they have started a new
1738 // stream, so we error out.
1739 connectionError(Http2Error::PROTOCOL_ERROR, "Peer refused to GOAWAY.");
1740 return;
1741 }
1742 } else if (streamWasResetLocally(streamID)) {
1743 qCDebug(qHttp2ConnectionLog,
1744 "[%p] Received HEADERS on previously locally reset stream %d (must process but ignore)",
1745 this, streamID);
1746 // nop
1747 } else if (auto it = m_streams.constFind(streamID); it == m_streams.cend()) {
1748 // RFC 9113, 6.2: HEADERS frames MUST be associated with a stream.
1749 // A connection error is not required but it seems to be the right thing to do.
1750 qCDebug(qHttp2ConnectionLog, "[%p] Received HEADERS on non-existent stream %d", this,
1751 streamID);
1752 return connectionError(PROTOCOL_ERROR, "HEADERS on invalid stream");
1753 } else if (isInvalidStream(streamID)) {
1754 // RFC 9113 6.4: After receiving a RST_STREAM on a stream, the receiver MUST NOT send
1755 // additional frames for that stream
1756 qCDebug(qHttp2ConnectionLog, "[%p] Received HEADERS on reset stream %d", this, streamID);
1757 return connectionError(ENHANCE_YOUR_CALM, "HEADERS on invalid stream");
1758 }
1759
1760 const auto flags = inboundFrame.flags();
1761 if (flags.testFlag(FrameFlag::PRIORITY)) {
1762 qCDebug(qHttp2ConnectionLog, "[%p] HEADERS frame on stream %d has PRIORITY flag", this,
1763 streamID);
1764 handlePRIORITY();
1765 }
1766
1767 const bool endHeaders = flags.testFlag(FrameFlag::END_HEADERS);
1768 continuedFrames.clear();
1769 m_headerBlockSize = 0;
1770 if (!validateHeaderListSize(inboundFrame))
1771 return;
1772 continuedFrames.push_back(std::move(inboundFrame));
1773 if (!endHeaders) {
1774 continuationExpected = true;
1775 return;
1776 }
1777
1778 handleContinuedHEADERS();
1779}
1780
1781void QHttp2Connection::handlePRIORITY()
1782{
1783 Q_ASSERT(inboundFrame.type() == FrameType::PRIORITY
1784 || inboundFrame.type() == FrameType::HEADERS);
1785
1786 const auto streamID = inboundFrame.streamID();
1787 if (streamIsIgnored(streamID))
1788 return;
1789
1790 // RFC 9913, 6.3: If a PRIORITY frame is received with a stream identifier of 0x00, the
1791 // recipient MUST respond with a connection error
1792 if (streamID == connectionStreamID)
1793 return connectionError(PROTOCOL_ERROR, "PRIORITY on 0x0 stream");
1794
1795 // RFC 9113 6.4: After receiving a RST_STREAM on a stream, the receiver MUST NOT send
1796 // additional frames for that stream
1797 if (isInvalidStream(streamID))
1798 return connectionError(ENHANCE_YOUR_CALM, "PRIORITY on invalid stream");
1799
1800 // RFC 9913, 6.3: A PRIORITY frame with a length other than 5 octets MUST be treated as a
1801 // stream error (Section 5.4.2) of type FRAME_SIZE_ERROR.
1802 // checked in Frame::validateHeader()
1803 Q_ASSERT(inboundFrame.type() != FrameType::PRIORITY || inboundFrame.payloadSize() == 5);
1804
1805 quint32 streamDependency = 0;
1806 uchar weight = 0;
1807 const bool noErr = inboundFrame.priority(&streamDependency, &weight);
1808 Q_UNUSED(noErr);
1809 Q_ASSERT(noErr);
1810
1811 const bool exclusive = streamDependency & 0x80000000;
1812 streamDependency &= ~0x80000000;
1813
1814 // Ignore this for now ...
1815 // Can be used for streams (re)prioritization - 5.3
1816 Q_UNUSED(exclusive);
1817 Q_UNUSED(weight);
1818}
1819
1820void QHttp2Connection::handleRST_STREAM()
1821{
1822 Q_ASSERT(inboundFrame.type() == FrameType::RST_STREAM);
1823
1824 const auto streamID = inboundFrame.streamID();
1825 if (streamIsIgnored(streamID))
1826 return;
1827
1828 // RFC 9113, 6.4: RST_STREAM frames MUST be associated with a stream.
1829 // If a RST_STREAM frame is received with a stream identifier of 0x0,
1830 // the recipient MUST treat this as a connection error (Section 5.4.1)
1831 // of type PROTOCOL_ERROR.
1832 if (streamID == connectionStreamID)
1833 return connectionError(PROTOCOL_ERROR, "RST_STREAM on 0x0");
1834
1835 // RFC 9113, 6.4: A RST_STREAM frame with a length other than 4 octets MUST be treated as a
1836 // connection error (Section 5.4.1) of type FRAME_SIZE_ERROR.
1837 // checked in Frame::validateHeader()
1838 Q_ASSERT(inboundFrame.payloadSize() == 4);
1839
1840 const auto error = qFromBigEndian<quint32>(inboundFrame.dataBegin());
1841 if (QPointer<QHttp2Stream> stream = m_streams[streamID])
1842 emit stream->rstFrameReceived(error);
1843
1844 // Verify that whatever stream is being RST'd is not in the idle state:
1845 const quint32 lastRelevantStreamID = [this, streamID]() {
1846 quint32 peerMask = m_connectionType == Type::Client ? 0 : 1;
1847 return ((streamID & 1) == peerMask) ? m_lastIncomingStreamID : m_nextStreamID - 2;
1848 }();
1849 if (streamID > lastRelevantStreamID) {
1850 // "RST_STREAM frames MUST NOT be sent for a stream
1851 // in the "idle" state. .. the recipient MUST treat this
1852 // as a connection error (Section 5.4.1) of type PROTOCOL_ERROR."
1853 return connectionError(PROTOCOL_ERROR, "RST_STREAM on idle stream");
1854 }
1855
1856 Q_ASSERT(inboundFrame.dataSize() == 4);
1857
1858 if (QPointer<QHttp2Stream> stream = m_streams[streamID])
1859 stream->handleRST_STREAM(inboundFrame);
1860}
1861
1862void QHttp2Connection::handleSETTINGS()
1863{
1864 // 6.5 SETTINGS.
1865 Q_ASSERT(inboundFrame.type() == FrameType::SETTINGS);
1866
1867 // RFC 9113, 6.5: If an endpoint receives a SETTINGS frame whose Stream Identifier field is
1868 // anything other than 0x00, the endpoint MUST respond with a connection error
1869 if (inboundFrame.streamID() != connectionStreamID)
1870 return connectionError(PROTOCOL_ERROR, "SETTINGS on invalid stream");
1871
1872 if (inboundFrame.flags().testFlag(FrameFlag::ACK)) {
1873 // RFC 9113, 6.5: Receipt of a SETTINGS frame with the ACK flag set and a length field
1874 // value other than 0 MUST be treated as a connection error
1875 if (inboundFrame.payloadSize())
1876 return connectionError(FRAME_SIZE_ERROR, "SETTINGS ACK with data");
1877 if (!waitingForSettingsACK)
1878 return connectionError(PROTOCOL_ERROR, "unexpected SETTINGS ACK");
1879 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS ACK", this);
1880 waitingForSettingsACK = false;
1881 return;
1882 }
1883 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS frame", this);
1884
1885 if (inboundFrame.dataSize()) {
1886 // RFC 9113, 6.5: A SETTINGS frame with a length other than a multiple of 6 octets MUST be
1887 // treated as a connection error (Section 5.4.1) of type FRAME_SIZE_ERROR.
1888 // checked in Frame::validateHeader()
1889 Q_ASSERT(inboundFrame.payloadSize() % 6 == 0);
1890
1891 auto src = inboundFrame.dataBegin();
1892 for (const uchar *end = src + inboundFrame.dataSize(); src != end; src += 6) {
1893 const Settings identifier = Settings(qFromBigEndian<quint16>(src));
1894 const quint32 intVal = qFromBigEndian<quint32>(src + 2);
1895 if (!acceptSetting(identifier, intVal)) {
1896 // If not accepted - we finish with connectionError.
1897 qCDebug(qHttp2ConnectionLog, "[%p] Received an unacceptable setting, %u, %u", this,
1898 quint32(identifier), intVal);
1899 return; // connectionError already called in acceptSetting.
1900 }
1901 }
1902 }
1903
1904 qCDebug(qHttp2ConnectionLog, "[%p] Sending SETTINGS ACK", this);
1905 sendSETTINGS_ACK();
1906 emit settingsFrameReceived();
1907}
1908
1909void QHttp2Connection::handlePUSH_PROMISE()
1910{
1911 // 6.6 PUSH_PROMISE.
1912 Q_ASSERT(inboundFrame.type() == FrameType::PUSH_PROMISE);
1913
1914 // RFC 9113, 6.6: PUSH_PROMISE MUST NOT be sent if the SETTINGS_ENABLE_PUSH setting of the peer
1915 // endpoint is set to 0. An endpoint that has set this setting and has received acknowledgment
1916 // MUST treat the receipt of a PUSH_PROMISE frame as a connection error
1917 if (!pushPromiseEnabled && !waitingForSettingsACK) {
1918 // This means, server ACKed our 'NO PUSH',
1919 // but sent us PUSH_PROMISE anyway.
1920 return connectionError(PROTOCOL_ERROR, "unexpected PUSH_PROMISE frame");
1921 }
1922
1923 // RFC 9113, 6.6: If the Stream Identifier field specifies the value 0x00, a recipient MUST
1924 // respond with a connection error.
1925 const auto streamID = inboundFrame.streamID();
1926 if (streamID == connectionStreamID)
1927 return connectionError(PROTOCOL_ERROR, "PUSH_PROMISE with invalid associated stream (0x0)");
1928
1929 auto it = m_streams.constFind(streamID);
1930#if 0 // Needs to be done after some timeout in case the stream has only just been reset
1931 if (it != m_streams.constEnd()) {
1932 QHttp2Stream *associatedStream = it->get();
1933 if (associatedStream->state() != QHttp2Stream::State::Open
1934 && associatedStream->state() != QHttp2Stream::State::HalfClosedLocal) {
1935 // Cause us to error out below:
1936 it = m_streams.constEnd();
1937 }
1938 }
1939#endif
1940 // RFC 9113, 6.6: PUSH_PROMISE frames MUST only be sent on a peer-initiated stream that
1941 // is in either the "open" or "half-closed (remote)" state.
1942
1943 // I.e. If you are the server then the client must have initiated the stream you are sending
1944 // the promise on. And since this is about _sending_ we have to invert "Remote" to "Local"
1945 // because we are receiving.
1946 if (it == m_streams.constEnd())
1947 return connectionError(ENHANCE_YOUR_CALM, "PUSH_PROMISE with invalid associated stream");
1948 if ((m_connectionType == Type::Client && (streamID & 1) == 0) ||
1949 (m_connectionType == Type::Server && (streamID & 1) == 1)) {
1950 return connectionError(ENHANCE_YOUR_CALM, "PUSH_PROMISE with invalid associated stream");
1951 }
1952 if ((*it)->state() != QHttp2Stream::State::Open &&
1953 (*it)->state() != QHttp2Stream::State::HalfClosedLocal) {
1954 return connectionError(ENHANCE_YOUR_CALM, "PUSH_PROMISE with invalid associated stream");
1955 }
1956
1957 // RFC 9113, 6.6: The promised stream identifier MUST be a valid choice for the
1958 // next stream sent by the sender
1959 const auto reservedID = qFromBigEndian<quint32>(inboundFrame.dataBegin());
1960 if ((reservedID & 1) || reservedID <= m_lastIncomingStreamID || reservedID > lastValidStreamID)
1961 return connectionError(PROTOCOL_ERROR, "PUSH_PROMISE with invalid promised stream ID");
1962
1963 bool streamCountIsOk = size_t(m_maxConcurrentStreams) > size_t(numActiveRemoteStreams());
1964 // RFC 9113, 6.6: A receiver MUST treat the receipt of a PUSH_PROMISE that promises an
1965 // illegal stream identifier (Section 5.1.1) as a connection error
1966 auto *stream = createStreamInternal_impl(reservedID);
1967 if (!stream)
1968 return connectionError(PROTOCOL_ERROR, "PUSH_PROMISE with already active stream ID");
1969 m_lastIncomingStreamID = reservedID;
1970 stream->setState(QHttp2Stream::State::ReservedRemote);
1971
1972 if (!streamCountIsOk) {
1973 stream->streamError(PROTOCOL_ERROR, QLatin1String("Max concurrent streams reached"));
1974 emit incomingStreamErrorOccured(CreateStreamError::MaxConcurrentStreamsReached);
1975 return;
1976 }
1977
1978 // "ignoring a PUSH_PROMISE frame causes the stream state to become
1979 // indeterminate" - let's send RST_STREAM frame with REFUSE_STREAM code.
1980 if (!pushPromiseEnabled) {
1981 return stream->streamError(REFUSE_STREAM,
1982 QLatin1String("PUSH_PROMISE not enabled but ignored"));
1983 }
1984
1985 // RFC 9113, 6.6: The total number of padding octets is determined by the value of the Pad
1986 // Length field. If the length of the padding is the length of the frame payload or greater,
1987 // the recipient MUST treat this as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
1988 // checked in Frame::validateHeader()
1989 Q_ASSERT(inboundFrame.dataSize() > inboundFrame.padding());
1990 const bool endHeaders = inboundFrame.flags().testFlag(FrameFlag::END_HEADERS);
1991 continuedFrames.clear();
1992 m_headerBlockSize = 0;
1993 if (!validateHeaderListSize(inboundFrame))
1994 return;
1995 continuedFrames.push_back(std::move(inboundFrame));
1996
1997 if (!endHeaders) {
1998 continuationExpected = true;
1999 return;
2000 }
2001
2002 handleContinuedHEADERS();
2003}
2004
2005void QHttp2Connection::handlePING()
2006{
2007 Q_ASSERT(inboundFrame.type() == FrameType::PING);
2008
2009 // RFC 9113, 6.7: PING frames are not associated with any individual stream. If a PING frame is
2010 // received with a Stream Identifier field value other than 0x00, the recipient MUST respond
2011 // with a connection error
2012 if (inboundFrame.streamID() != connectionStreamID)
2013 return connectionError(PROTOCOL_ERROR, "PING on invalid stream");
2014
2015 // Receipt of a PING frame with a length field value other than 8 MUST be treated
2016 // as a connection error (Section 5.4.1) of type FRAME_SIZE_ERROR.
2017 // checked in Frame::validateHeader()
2018 Q_ASSERT(inboundFrame.payloadSize() == 8);
2019
2020 if (inboundFrame.flags() & FrameFlag::ACK) {
2021 QByteArrayView pingSignature(reinterpret_cast<const char *>(inboundFrame.dataBegin()), 8);
2022 if (!m_lastPingSignature.has_value()) {
2023 emit pingFrameReceived(PingState::PongNoPingSent);
2024 qCWarning(qHttp2ConnectionLog, "[%p] PING with ACK received but no PING was sent.", this);
2025 } else if (pingSignature != m_lastPingSignature) {
2026 emit pingFrameReceived(PingState::PongSignatureChanged);
2027 qCWarning(qHttp2ConnectionLog, "[%p] PING signature does not match the last PING.", this);
2028 } else {
2029 emit pingFrameReceived(PingState::PongSignatureIdentical);
2030 }
2031 m_lastPingSignature.reset();
2032
2033 // Handle sendInitialServerGracefulShutdownGoaway()
2034 if (m_gracefulShutdownState == GracefulShutdownState::AwaitingShutdownPing) {
2035 sendFinalServerGracefulShutdownGoaway();
2036 } else if (m_gracefulShutdownState == GracefulShutdownState::AwaitingPriorPing) {
2037 // Prior PING completed, now send our RTT measurement PING. This shouldn't fail!
2038 m_gracefulShutdownState = GracefulShutdownState::AwaitingShutdownPing;
2039 [[maybe_unused]] const bool ok = sendPing();
2040 Q_ASSERT(ok);
2041 }
2042
2043 return;
2044 } else {
2045 emit pingFrameReceived(PingState::Ping);
2046
2047 }
2048
2049
2050 frameWriter.start(FrameType::PING, FrameFlag::ACK, connectionStreamID);
2051 frameWriter.append(inboundFrame.dataBegin(), inboundFrame.dataBegin() + 8);
2052 frameWriter.write(*getSocket());
2053}
2054
2055void QHttp2Connection::handleGOAWAY()
2056{
2057 // 6.8 GOAWAY
2058
2059 Q_ASSERT(inboundFrame.type() == FrameType::GOAWAY);
2060 // RFC 9113, 6.8: An endpoint MUST treat a GOAWAY frame with a stream identifier
2061 // other than 0x0 as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
2062 if (inboundFrame.streamID() != connectionStreamID)
2063 return connectionError(PROTOCOL_ERROR, "GOAWAY on invalid stream");
2064
2065 // RFC 9113, 6.8:
2066 // Reserved (1) + Last-Stream-ID (31) + Error Code (32) + Additional Debug Data (..)
2067 // checked in Frame::validateHeader()
2068 Q_ASSERT(inboundFrame.payloadSize() >= 8);
2069
2070 const uchar *const src = inboundFrame.dataBegin();
2071 // RFC 9113, 4.1: 31-bit Stream ID; lastValidStreamID(0x7FFFFFFF) masks out the reserved MSB
2072 const quint32 lastStreamID = qFromBigEndian<quint32>(src) & lastValidStreamID;
2073 const Http2Error errorCode = Http2Error(qFromBigEndian<quint32>(src + 4));
2074
2075 // 6.8 "the GOAWAY contains the stream identifier of the last peer-initiated stream that was
2076 // or might be processed on the sending endpoint in this connection."
2077 // Alternatively, they can specify 0 as the last stream ID, meaning they are not intending to
2078 // process any remaining stream(s).
2079 const quint32 LocalMask = m_connectionType == Type::Client ? 1 : 0;
2080 // The stream must match the LocalMask, meaning we initiated it, for the last stream ID to make
2081 // sense - they are not processing their own streams.
2082 if (lastStreamID != 0 && (lastStreamID & 0x1) != LocalMask)
2083 return connectionError(PROTOCOL_ERROR, "GOAWAY with invalid last stream ID");
2084
2085 // 6.8 - An endpoint MAY send multiple GOAWAY frames if circumstances
2086 // change. Endpoints MUST NOT increase the value they send in the last
2087 // stream identifier
2088 if (m_lastGoAwayLastStreamID && lastStreamID > *m_lastGoAwayLastStreamID)
2089 return connectionError(PROTOCOL_ERROR, "Repeated GOAWAY with invalid last stream ID");
2090 m_lastGoAwayLastStreamID = lastStreamID;
2091
2092 qCDebug(qHttp2ConnectionLog, "[%p] Received GOAWAY frame, error code %u, last stream %u",
2093 this, errorCode, lastStreamID);
2094 m_goingAway = true;
2095
2096 emit receivedGOAWAY(errorCode, lastStreamID);
2097
2098 if (errorCode == HTTP2_NO_ERROR) {
2099 // Graceful GOAWAY (NO_ERROR): Only cancel streams the peer explicitly won't process
2100 // (those with IDs > lastStreamID). Streams with ID <= lastStreamID can still complete.
2101 // '0' can be used in the special case that no streams at all were or will be processed.
2102 const quint32 firstPossibleStream = m_connectionType == Type::Client ? 1 : 2;
2103 const quint32 firstCancelledStream = lastStreamID ? lastStreamID + 2 : firstPossibleStream;
2104 Q_ASSERT((firstCancelledStream & 0x1) == LocalMask);
2105 for (quint32 id = firstCancelledStream; id < m_nextStreamID; id += 2) {
2106 QHttp2Stream *stream = m_streams.value(id, nullptr);
2107 if (stream && stream->isActive())
2108 stream->finishWithError(errorCode, "Received GOAWAY"_L1);
2109 }
2110 maybeCloseOnGoingAway(); // check if we can close now
2111 } else {
2112 // RFC 9113, 5.4.1: After sending the GOAWAY frame for an error
2113 // condition, the endpoint MUST close the TCP connection.
2114 // As the peer is closing the connection immediately, they won't
2115 // process any more data, so we close the connection here already.
2116 m_connectionAborted = true;
2117 for (QHttp2Stream *stream : std::as_const(m_streams)) {
2118 if (stream && stream->isActive())
2119 stream->finishWithError(errorCode, "Received GOAWAY"_L1);
2120 }
2121 closeSession();
2122 }
2123}
2124
2125void QHttp2Connection::handleWINDOW_UPDATE()
2126{
2127 Q_ASSERT(inboundFrame.type() == FrameType::WINDOW_UPDATE);
2128
2129 const quint32 delta = qFromBigEndian<quint32>(inboundFrame.dataBegin());
2130 // RFC 9113, 6.9: A receiver MUST treat the receipt of a WINDOW_UPDATE frame with a
2131 // flow-control window increment of 0 as a stream error (Section 5.4.2) of type PROTOCOL_ERROR;
2132 // errors on the connection flow-control window MUST be treated as a connection error
2133 const bool valid = delta && delta <= quint32(std::numeric_limits<qint32>::max());
2134 const auto streamID = inboundFrame.streamID();
2135 if (streamIsIgnored(streamID))
2136 return;
2137
2138 // RFC 9113, 6.9: A WINDOW_UPDATE frame with a length other than 4 octets MUST be treated
2139 // as a connection error (Section 5.4.1) of type FRAME_SIZE_ERROR.
2140 // checked in Frame::validateHeader()
2141 Q_ASSERT(inboundFrame.payloadSize() == 4);
2142
2143 qCDebug(qHttp2ConnectionLog(), "[%p] Received WINDOW_UPDATE, stream %d, delta %d", this,
2144 streamID, delta);
2145 if (streamID == connectionStreamID) {
2146 if (!valid)
2147 return connectionError(PROTOCOL_ERROR, "WINDOW_UPDATE invalid delta");
2148 qint32 sum = 0;
2149 // RFC 9113, 6.9.1: a WINDOW_UPDATE that pushes the connection window past 2^31-1 is a
2150 // connection error of type FLOW_CONTROL_ERROR (the sender sends GOAWAY).
2151 if (qAddOverflow(sessionSendWindowSize, qint32(delta), &sum))
2152 return connectionError(FLOW_CONTROL_ERROR, "WINDOW_UPDATE exceeds maximum window");
2153 sessionSendWindowSize = sum;
2154
2155 // Stream may have been unblocked, so maybe try to write again:
2156 const auto blockedStreams = std::exchange(m_blockedStreams, {});
2157 for (quint32 blockedStreamID : blockedStreams) {
2158 const QPointer<QHttp2Stream> stream = m_streams.value(blockedStreamID);
2159 if (!stream || !stream->isActive() || !stream->isUploadingDATA())
2160 continue;
2161 if (stream->isUploadBlocked()) {
2162 m_blockedStreams.insert(blockedStreamID);
2163
2164
2165 } else {
2166 QMetaObject::invokeMethod(stream, &QHttp2Stream::maybeResumeUpload,
2167 Qt::QueuedConnection);
2168 }
2169 }
2170 } else {
2171 QHttp2Stream *stream = m_streams.value(streamID);
2172 if (!stream || !stream->isActive()) {
2173 // WINDOW_UPDATE on closed streams can be ignored.
2174 qCDebug(qHttp2ConnectionLog, "[%p] Received WINDOW_UPDATE on closed stream %d", this,
2175 streamID);
2176 return;
2177 } else if (!valid) {
2178 return stream->streamError(PROTOCOL_ERROR,
2179 QLatin1String("WINDOW_UPDATE invalid delta"));
2180 }
2181 stream->handleWINDOW_UPDATE(inboundFrame);
2182 }
2183}
2184
2185void QHttp2Connection::handleCONTINUATION()
2186{
2187 Q_ASSERT(inboundFrame.type() == FrameType::CONTINUATION);
2188 if (inboundFrame.payloadSize() > m_config.maxFrameSize()) {
2189 qCDebug(qHttp2ConnectionLog,
2190 "[%p] Received CONTINUATION frame with payload size %u, "
2191 "but SETTINGS_MAX_FRAME_SIZE is %u, sending FRAME_SIZE_ERROR",
2192 this, inboundFrame.payloadSize(), m_config.maxFrameSize());
2193 return connectionError(Http2Error::FRAME_SIZE_ERROR,
2194 "CONTINUATION payload size exceeds SETTINGS_MAX_FRAME_SIZE");
2195 }
2196 auto streamID = inboundFrame.streamID();
2197 qCDebug(qHttp2ConnectionLog,
2198 "[%p] Received CONTINUATION frame on stream %d, end stream? %s", this, streamID,
2199 inboundFrame.flags().testFlag(Http2::FrameFlag::END_STREAM) ? "yes" : "no");
2200 if (continuedFrames.empty())
2201 return connectionError(PROTOCOL_ERROR,
2202 "CONTINUATION without a preceding HEADERS or PUSH_PROMISE");
2203 if (!continuationExpected)
2204 return connectionError(PROTOCOL_ERROR,
2205 "CONTINUATION after a frame with the END_HEADERS flag set");
2206
2207 if (inboundFrame.streamID() != continuedFrames.front().streamID())
2208 return connectionError(PROTOCOL_ERROR, "CONTINUATION on invalid stream");
2209
2210 const bool endHeaders = inboundFrame.flags().testFlag(FrameFlag::END_HEADERS);
2211 // No reset here: this frame continues the block begun by HEADERS/PUSH_PROMISE,
2212 // so it must add to the running total rather than start a new one.
2213 if (!validateHeaderListSize(inboundFrame))
2214 return;
2215 continuedFrames.push_back(std::move(inboundFrame));
2216
2217 if (!endHeaders)
2218 return;
2219
2220 continuationExpected = false;
2221 handleContinuedHEADERS();
2222}
2223
2224bool QHttp2Connection::validateHeaderListSize(const Frame &frame)
2225{
2226 const quint32 limit =
2227 QHttp2ConfigurationPrivate::get(std::as_const(m_config))->maxHeaderListSize;
2228 if (limit == std::numeric_limits<quint32>::max())
2229 return true;
2230 // Conservative pre-decode resource check. The compressed header block cannot
2231 // exceed the decoded header list size, so decoding would inevitably exceed
2232 // SETTINGS_MAX_HEADER_LIST_SIZE. Reject the request early with
2233 // ENHANCE_YOUR_CALM rather than attempting HPACK decoding.
2234 m_headerBlockSize += frame.hpackBlockSize();
2235 if (m_headerBlockSize > limit) {
2236 connectionError(ENHANCE_YOUR_CALM, "Header list size limit exceeded");
2237 return false;
2238 }
2239 return true;
2240}
2241
2242void QHttp2Connection::handleContinuedHEADERS()
2243{
2244 // 'Continued' HEADERS can be: the initial HEADERS/PUSH_PROMISE frame
2245 // with/without END_HEADERS flag set plus, if no END_HEADERS flag,
2246 // a sequence of one or more CONTINUATION frames.
2247 Q_ASSERT(!continuedFrames.empty());
2248 const auto firstFrameType = continuedFrames[0].type();
2249 Q_ASSERT(firstFrameType == FrameType::HEADERS || firstFrameType == FrameType::PUSH_PROMISE);
2250
2251 const auto streamID = continuedFrames[0].streamID();
2252
2253 const auto streamIt = m_streams.constFind(streamID);
2254 if (firstFrameType == FrameType::HEADERS) {
2255 if (streamIt != m_streams.cend() && !streamWasResetLocally(streamID)) {
2256 QHttp2Stream *stream = streamIt.value();
2257 if (stream->state() != QHttp2Stream::State::HalfClosedLocal
2258 && stream->state() != QHttp2Stream::State::ReservedRemote
2259 && stream->state() != QHttp2Stream::State::Idle
2260 && stream->state() != QHttp2Stream::State::Open) {
2261 // We can receive HEADERS on streams initiated by our requests
2262 // (these streams are in halfClosedLocal or open state) or
2263 // remote-reserved streams from a server's PUSH_PROMISE.
2264 return stream->streamError(PROTOCOL_ERROR, "HEADERS on invalid stream"_L1);
2265 }
2266 }
2267 // Else: we cannot just ignore our peer's HEADERS frames - they change
2268 // HPACK context - even though the stream was reset; apparently the peer
2269 // has yet to see the reset.
2270 }
2271
2272 std::vector<uchar> hpackBlock(assemble_hpack_block(continuedFrames));
2273 const bool hasHeaderFields = !hpackBlock.empty();
2274 if (hasHeaderFields) {
2275 HPack::BitIStream inputStream{ hpackBlock.data(), hpackBlock.data() + hpackBlock.size() };
2276 if (!decoder.decodeHeaderFields(inputStream))
2277 return connectionError(COMPRESSION_ERROR, "HPACK decompression failed");
2278 } else {
2279 if (firstFrameType == FrameType::PUSH_PROMISE) {
2280 // It could be a PRIORITY sent in HEADERS - already handled by this
2281 // point in handleHEADERS. If it was PUSH_PROMISE (HTTP/2 8.2.1):
2282 // "The header fields in PUSH_PROMISE and any subsequent CONTINUATION
2283 // frames MUST be a valid and complete set of request header fields
2284 // (Section 8.1.2.3) ... If a client receives a PUSH_PROMISE that does
2285 // not include a complete and valid set of header fields or the :method
2286 // pseudo-header field identifies a method that is not safe, it MUST
2287 // respond with a stream error (Section 5.4.2) of type PROTOCOL_ERROR."
2288 if (streamIt != m_streams.cend()) {
2289 (*streamIt)->streamError(PROTOCOL_ERROR,
2290 QLatin1String("PUSH_PROMISE with incomplete headers"));
2291 }
2292 return;
2293 }
2294
2295 // We got back an empty hpack block. Now let's figure out if there was an error.
2296 constexpr auto hpackBlockHasContent = [](const auto &c) { return c.hpackBlockSize() > 0; };
2297 const bool anyHpackBlock = std::any_of(continuedFrames.cbegin(), continuedFrames.cend(),
2298 hpackBlockHasContent);
2299 if (anyHpackBlock) // There was hpack block data, but returned empty => it overflowed.
2300 return connectionError(FRAME_SIZE_ERROR, "HEADERS frame too large");
2301 }
2302
2303 if (streamWasResetLocally(streamID) || streamIt == m_streams.cend())
2304 return; // No more processing without a stream from here on.
2305 if (streamIsIgnored(streamID)) {
2306 // Stream was established after GOAWAY cut-off, we ignore it, but we
2307 // have to process things that alter state. That already happened, so we
2308 // stop here.
2309 if (continuedFrames[0].flags().testFlag(Http2::FrameFlag::END_STREAM)) {
2310 if (QHttp2Stream *stream = streamIt.value()) {
2311 stream->setState(QHttp2Stream::State::Closed);
2312 delete stream;
2313 }
2314 }
2315 return;
2316 }
2317
2318 switch (firstFrameType) {
2319 case FrameType::HEADERS:
2320 streamIt.value()->handleHEADERS(continuedFrames[0].flags(), decoder.decodedHeader());
2321 break;
2322 case FrameType::PUSH_PROMISE: {
2323 std::optional<QUrl> promiseKey = HPack::makePromiseKeyUrl(decoder.decodedHeader());
2324 if (!promiseKey)
2325 return; // invalid URL/key !
2326 if (m_promisedStreams.contains(*promiseKey))
2327 return; // already promised!
2328 const auto promiseID = qFromBigEndian<quint32>(continuedFrames[0].dataBegin());
2329 QHttp2Stream *stream = m_streams.value(promiseID);
2330 stream->transitionState(QHttp2Stream::StateTransition::CloseLocal);
2331 stream->handleHEADERS(continuedFrames[0].flags(), decoder.decodedHeader());
2332 emit newPromisedStream(stream); // @future[consider] add promise key as argument?
2333 m_promisedStreams.emplace(*promiseKey, promiseID);
2334 break;
2335 }
2336 default:
2337 break;
2338 }
2339}
2340
2341bool QHttp2Connection::acceptSetting(Http2::Settings identifier, quint32 newValue)
2342{
2343 switch (identifier) {
2344 case Settings::HEADER_TABLE_SIZE_ID: {
2345 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS HEADER_TABLE_SIZE %d", this, newValue);
2346 if (newValue > maxAcceptableTableSize) {
2347 connectionError(PROTOCOL_ERROR, "SETTINGS invalid table size");
2348 return false;
2349 }
2350 if (!pendingTableSizeUpdates[0] && encoder.dynamicTableCapacity() == newValue) {
2351 qCDebug(qHttp2ConnectionLog,
2352 "[%p] Ignoring SETTINGS HEADER_TABLE_SIZE %d (same as current value)", this,
2353 newValue);
2354 break;
2355 }
2356
2357 if (pendingTableSizeUpdates[0].value_or(std::numeric_limits<quint32>::max()) >= newValue) {
2358 pendingTableSizeUpdates[0] = newValue;
2359 pendingTableSizeUpdates[1].reset(); // 0 is the latest _and_ smallest, so we don't need 1
2360 qCDebug(qHttp2ConnectionLog, "[%p] Pending table size update to %u", this, newValue);
2361 } else {
2362 pendingTableSizeUpdates[1] = newValue; // newValue was larger than 0, so it goes to 1
2363 qCDebug(qHttp2ConnectionLog, "[%p] Pending 2nd table size update to %u, smallest is %u",
2364 this, newValue, *pendingTableSizeUpdates[0]);
2365 }
2366 break;
2367 }
2368 case Settings::INITIAL_WINDOW_SIZE_ID: {
2369 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS INITIAL_WINDOW_SIZE %d", this,
2370 newValue);
2371 // For every active stream - adjust its window
2372 // (and handle possible overflows as errors).
2373 if (newValue > quint32(std::numeric_limits<qint32>::max())) {
2374 connectionError(FLOW_CONTROL_ERROR, "SETTINGS invalid initial window size");
2375 return false;
2376 }
2377
2378 const qint32 delta = qint32(newValue) - streamInitialSendWindowSize;
2379 streamInitialSendWindowSize = qint32(newValue);
2380
2381 qCDebug(qHttp2ConnectionLog, "[%p] Adjusting initial window size for %zu streams by %d",
2382 this, size_t(m_streams.size()), delta);
2383 for (const QPointer<QHttp2Stream> &stream : std::as_const(m_streams)) {
2384 if (!stream)
2385 continue;
2386 qint32 sum = 0;
2387 // RFC 9113, 6.9.2: a SETTINGS_INITIAL_WINDOW_SIZE change that pushes any
2388 // flow-control window past 2^31-1 is a connection error of type FLOW_CONTROL_ERROR.
2389 if (qAddOverflow(stream->m_sendWindow, delta, &sum)) {
2390 connectionError(FLOW_CONTROL_ERROR,
2391 "SETTINGS_INITIAL_WINDOW_SIZE overflowed a flow-control window");
2392 return false;
2393 }
2394 stream->m_sendWindow = sum;
2395 if (delta > 0 && stream->isUploadingDATA() && !stream->isUploadBlocked()) {
2396 QMetaObject::invokeMethod(stream, &QHttp2Stream::maybeResumeUpload,
2397 Qt::QueuedConnection);
2398 }
2399 }
2400 break;
2401 }
2402 case Settings::MAX_CONCURRENT_STREAMS_ID: {
2403 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS MAX_CONCURRENT_STREAMS %d", this,
2404 newValue);
2405 m_peerMaxConcurrentStreams = newValue;
2406 break;
2407 }
2408 case Settings::MAX_FRAME_SIZE_ID: {
2409 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS MAX_FRAME_SIZE %d", this, newValue);
2410 if (newValue < Http2::minPayloadLimit || newValue > Http2::maxPayloadSize) {
2411 connectionError(PROTOCOL_ERROR, "SETTINGS max frame size is out of range");
2412 return false;
2413 }
2414 maxFrameSize = newValue;
2415 break;
2416 }
2417 case Settings::MAX_HEADER_LIST_SIZE_ID: {
2418 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS MAX_HEADER_LIST_SIZE %d", this,
2419 newValue);
2420 // We just remember this value, it can later
2421 // prevent us from sending any request (and this
2422 // will end up in request/reply error).
2423 m_maxHeaderListSize = newValue;
2424 break;
2425 }
2426 case Http2::Settings::ENABLE_PUSH_ID:
2427 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS ENABLE_PUSH %d", this, newValue);
2428 if (newValue != 0 && newValue != 1) {
2429 connectionError(PROTOCOL_ERROR, "SETTINGS peer sent illegal value for ENABLE_PUSH");
2430 return false;
2431 }
2432 if (m_connectionType == Type::Client) {
2433 if (newValue == 1) {
2434 connectionError(PROTOCOL_ERROR, "SETTINGS server sent ENABLE_PUSH=1");
2435 return false;
2436 }
2437 } else { // server-side
2438 pushPromiseEnabled = newValue;
2439 break;
2440 }
2441 }
2442
2443 return true;
2444}
2445
2446QT_END_NAMESPACE
2447
2448#include "moc_qhttp2connection_p.cpp"
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)