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