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