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