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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qtbase/src/network/access/qhttp2connection.cpp