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 if (connection->m_connectionType == QHttp2Connection::Type::Client) {
518 if (!connection->encoder.encodeRequest(outputStream, header: headers))
519 return false;
520 } else {
521 if (!connection->encoder.encodeResponse(outputStream, header: headers))
522 return false;
523 }
524
525 bool result = frameWriter.writeHEADERS(socket&: *socket, sizeLimit: connection->maxFrameSize);
526 if (endStream)
527 transitionState(transition: StateTransition::CloseLocal);
528
529 return result;
530}
531
532/*!
533 Sends a WINDOW_UPDATE frame with the given \a delta.
534 This increases our receive window size for this stream, allowing the remote
535 peer to send more data.
536*/
537void QHttp2Stream::sendWINDOW_UPDATE(quint32 delta)
538{
539 QHttp2Connection *connection = getConnection();
540 m_recvWindow += qint32(delta);
541 connection->sendWINDOW_UPDATE(streamID: streamID(), delta);
542}
543
544void QHttp2Stream::uploadDeviceDestroyed()
545{
546 if (isUploadingDATA()) {
547 // We're in the middle of sending DATA frames, we need to abort
548 // the stream.
549 streamError(errorCode: CANCEL, message: QLatin1String("Upload device destroyed while uploading"));
550 emit uploadDeviceError(errorString: "Upload device destroyed while uploading"_L1);
551 }
552 m_uploadDevice = nullptr;
553}
554
555void QHttp2Stream::setState(State newState)
556{
557 if (m_state == newState)
558 return;
559 qCDebug(qHttp2ConnectionLog, "[%p] stream %u, state changed from %d to %d", getConnection(),
560 streamID(), int(m_state), int(newState));
561 m_state = newState;
562 emit stateChanged(newState);
563}
564
565// Changes the state as appropriate given the current state and the transition.
566// Always call this before emitting any signals since the recipient might rely
567// on the new state!
568void QHttp2Stream::transitionState(StateTransition transition)
569{
570 switch (m_state) {
571 case State::Idle:
572 if (transition == StateTransition::Open)
573 setState(State::Open);
574 else
575 Q_UNREACHABLE(); // We should transition to Open before ever getting here
576 break;
577 case State::Open:
578 switch (transition) {
579 case StateTransition::CloseLocal:
580 setState(State::HalfClosedLocal);
581 break;
582 case StateTransition::CloseRemote:
583 setState(State::HalfClosedRemote);
584 break;
585 case StateTransition::RST:
586 setState(State::Closed);
587 break;
588 case StateTransition::Open: // no-op
589 break;
590 }
591 break;
592 case State::HalfClosedLocal:
593 if (transition == StateTransition::CloseRemote || transition == StateTransition::RST)
594 setState(State::Closed);
595 break;
596 case State::HalfClosedRemote:
597 if (transition == StateTransition::CloseLocal || transition == StateTransition::RST)
598 setState(State::Closed);
599 break;
600 case State::ReservedRemote:
601 if (transition == StateTransition::RST) {
602 setState(State::Closed);
603 } else if (transition == StateTransition::CloseLocal) { // Receiving HEADER closes local
604 setState(State::HalfClosedLocal);
605 }
606 break;
607 case State::Closed:
608 break;
609 }
610}
611
612void QHttp2Stream::handleDATA(const Frame &inboundFrame)
613{
614 QHttp2Connection *connection = getConnection();
615
616 qCDebug(qHttp2ConnectionLog, "[%p] stream %u, received DATA frame with payload of %u bytes",
617 connection, m_streamID, inboundFrame.payloadSize());
618
619 // RFC 9113, 6.1: If a DATA frame is received whose stream is not in the "open" or "half-closed
620 // (local)" state, the recipient MUST respond with a stream error (Section 5.4.2) of type
621 // STREAM_CLOSED;
622 // checked in QHttp2Connection
623 Q_ASSERT(state() != State::HalfClosedRemote && state() != State::Closed);
624
625 if (qint32(inboundFrame.payloadSize()) > m_recvWindow) {
626 qCDebug(qHttp2ConnectionLog,
627 "[%p] stream %u, received DATA frame with payload size %u, "
628 "but recvWindow is %d, sending FLOW_CONTROL_ERROR",
629 connection, m_streamID, inboundFrame.payloadSize(), m_recvWindow);
630 return streamError(errorCode: FLOW_CONTROL_ERROR, message: QLatin1String("data bigger than window size"));
631 }
632 // RFC 9113, 6.1: The total number of padding octets is determined by the value of the Pad
633 // Length field. If the length of the padding is the length of the frame payload or greater,
634 // the recipient MUST treat this as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
635 // checked in Framereader
636 Q_ASSERT(inboundFrame.buffer.size() >= frameHeaderSize);
637 Q_ASSERT(inboundFrame.payloadSize() + frameHeaderSize == inboundFrame.buffer.size());
638
639 m_recvWindow -= qint32(inboundFrame.payloadSize());
640 const bool endStream = inboundFrame.flags().testFlag(flag: FrameFlag::END_STREAM);
641 // Uncompress data if needed and append it ...
642 if (inboundFrame.dataSize() > 0 || endStream) {
643 QByteArray fragment(reinterpret_cast<const char *>(inboundFrame.dataBegin()),
644 inboundFrame.dataSize());
645 if (endStream)
646 transitionState(transition: StateTransition::CloseRemote);
647 emit dataReceived(data: fragment, endStream);
648 m_downloadBuffer.append(bd: std::move(fragment));
649 }
650
651 if (!endStream && m_recvWindow < connection->streamInitialReceiveWindowSize / 2) {
652 // @future[consider]: emit signal instead
653 sendWINDOW_UPDATE(delta: quint32(connection->streamInitialReceiveWindowSize - m_recvWindow));
654 }
655}
656
657void QHttp2Stream::handleHEADERS(Http2::FrameFlags frameFlags, const HPack::HttpHeader &headers)
658{
659 if (m_state == State::Idle)
660 transitionState(transition: StateTransition::Open);
661 const bool endStream = frameFlags.testFlag(flag: FrameFlag::END_STREAM);
662 if (endStream)
663 transitionState(transition: StateTransition::CloseRemote);
664 if (!headers.empty()) {
665 m_headers.insert(position: m_headers.end(), first: headers.begin(), last: headers.end());
666 emit headersUpdated();
667 }
668 emit headersReceived(headers, endStream);
669}
670
671void QHttp2Stream::handleRST_STREAM(const Frame &inboundFrame)
672{
673 transitionState(transition: StateTransition::RST);
674 m_RST_STREAM_received = qFromBigEndian<quint32>(src: inboundFrame.dataBegin());
675 if (isUploadingDATA()) {
676 disconnect(sender: m_uploadByteDevice, signal: nullptr, receiver: this, member: nullptr);
677 m_uploadDevice = nullptr;
678 m_uploadByteDevice = nullptr;
679 }
680 finishWithError(errorCode: Http2Error(*m_RST_STREAM_received));
681}
682
683void QHttp2Stream::handleWINDOW_UPDATE(const Frame &inboundFrame)
684{
685 const quint32 delta = qFromBigEndian<quint32>(src: inboundFrame.dataBegin());
686 const bool valid = delta && delta <= quint32(std::numeric_limits<qint32>::max());
687 qint32 sum = 0;
688 if (!valid || qAddOverflow(v1: m_sendWindow, v2: qint32(delta), r: &sum)) {
689 qCDebug(qHttp2ConnectionLog,
690 "[%p] stream %u, received WINDOW_UPDATE frame with invalid delta %u, sending "
691 "PROTOCOL_ERROR",
692 getConnection(), m_streamID, delta);
693 return streamError(errorCode: PROTOCOL_ERROR, message: "invalid WINDOW_UPDATE delta"_L1);
694 }
695 m_sendWindow = sum;
696 // Stream may have been unblocked, so maybe try to write again
697 if (isUploadingDATA())
698 maybeResumeUpload();
699}
700
701/*!
702 \class QHttp2Connection
703 \inmodule QtNetwork
704 \internal
705
706 The QHttp2Connection class represents a HTTP/2 connection.
707 It can only be created through the static functions
708 createDirectConnection(), createUpgradedConnection(),
709 and createDirectServerConnection().
710
711 createDirectServerConnection() is used for server-side connections, and has
712 certain limitations that a client does not.
713
714 As a client you can create a QHttp2Stream with createStream().
715
716 \sa QHttp2Stream
717*/
718
719/*!
720 \fn void QHttp2Connection::newIncomingStream(QHttp2Stream *stream)
721
722 This signal is emitted when a new \a stream is received from the remote
723 peer.
724*/
725
726/*!
727 \fn void QHttp2Connection::newPromisedStream(QHttp2Stream *stream)
728
729 This signal is emitted when the remote peer has promised a new \a stream.
730*/
731
732/*!
733 \fn void QHttp2Connection::errorReceived()
734
735 This signal is emitted when the connection has received an error.
736*/
737
738/*!
739 \fn void QHttp2Connection::connectionClosed()
740
741 This signal is emitted when the connection has been closed.
742*/
743
744/*!
745 \fn void QHttp2Connection::settingsFrameReceived()
746
747 This signal is emitted when the connection has received a SETTINGS frame.
748*/
749
750/*!
751 \fn void QHttp2Connection::errorOccurred(Http2::Http2Error errorCode, const QString &errorString)
752
753 This signal is emitted when the connection has encountered an error. The
754 \a errorCode parameter is the HTTP/2 error code, and the \a errorString
755 parameter is a human-readable description of the error.
756*/
757
758/*!
759 \fn void QHttp2Connection::receivedGOAWAY(Http2::Http2Error errorCode, quint32 lastStreamID)
760
761 This signal is emitted when the connection has received a GOAWAY frame. The
762 \a errorCode parameter is the HTTP/2 error code, and the \a lastStreamID
763 parameter is the last stream ID that the remote peer will process.
764
765 Any streams of a higher stream ID created by us will be ignored or reset.
766*/
767
768/*!
769 Create a new HTTP2 connection given a \a config and a \a socket.
770 This function assumes that the Upgrade headers etc. in http/1 have already
771 been sent and that the connection is already upgraded to http/2.
772
773 The object returned will be a child to the \a socket, or null on failure.
774*/
775QHttp2Connection *QHttp2Connection::createUpgradedConnection(QIODevice *socket,
776 const QHttp2Configuration &config)
777{
778 Q_ASSERT(socket);
779
780 auto connection = std::unique_ptr<QHttp2Connection>(new QHttp2Connection(socket));
781 connection->setH2Configuration(config);
782 connection->m_connectionType = QHttp2Connection::Type::Client;
783 // HTTP2 connection is already established and request was sent, so stream 1
784 // is already 'active' and is closed for any further outgoing data.
785 QHttp2Stream *stream = connection->createStreamInternal().unwrap();
786 Q_ASSERT(stream->streamID() == 1);
787 stream->setState(QHttp2Stream::State::HalfClosedLocal);
788 connection->m_upgradedConnection = true;
789
790 if (!connection->sendClientPreface()) {
791 qCWarning(qHttp2ConnectionLog, "[%p] Failed to send client preface", connection.get());
792 return nullptr;
793 }
794
795 return connection.release();
796}
797
798/*!
799 Create a new HTTP2 connection given a \a config and a \a socket.
800 This function will immediately send the client preface.
801
802 The object returned will be a child to the \a socket, or null on failure.
803*/
804QHttp2Connection *QHttp2Connection::createDirectConnection(QIODevice *socket,
805 const QHttp2Configuration &config)
806{
807 auto connection = std::unique_ptr<QHttp2Connection>(new QHttp2Connection(socket));
808 connection->setH2Configuration(config);
809 connection->m_connectionType = QHttp2Connection::Type::Client;
810
811 if (!connection->sendClientPreface()) {
812 qCWarning(qHttp2ConnectionLog, "[%p] Failed to send client preface", connection.get());
813 return nullptr;
814 }
815
816 return connection.release();
817}
818
819/*!
820 Create a new HTTP2 connection given a \a config and a \a socket.
821
822 The object returned will be a child to the \a socket, or null on failure.
823*/
824QHttp2Connection *QHttp2Connection::createDirectServerConnection(QIODevice *socket,
825 const QHttp2Configuration &config)
826{
827 auto connection = std::unique_ptr<QHttp2Connection>(new QHttp2Connection(socket));
828 connection->setH2Configuration(config);
829 connection->m_connectionType = QHttp2Connection::Type::Server;
830
831 connection->m_nextStreamID = 2; // server-initiated streams must be even
832
833 connection->m_waitingForClientPreface = true;
834
835 return connection.release();
836}
837
838/*!
839 Creates a stream on this connection.
840
841 Automatically picks the next available stream ID and returns a pointer to
842 the new stream, if possible. Otherwise returns an error.
843
844 \sa QHttp2Connection::CreateStreamError, QHttp2Stream
845*/
846QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError> QHttp2Connection::createStream()
847{
848 Q_ASSERT(m_connectionType == Type::Client); // This overload is just for clients
849 if (m_nextStreamID > lastValidStreamID)
850 return { QHttp2Connection::CreateStreamError::StreamIdsExhausted };
851 return createStreamInternal();
852}
853
854QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError>
855QHttp2Connection::createStreamInternal()
856{
857 if (m_goingAway)
858 return { QHttp2Connection::CreateStreamError::ReceivedGOAWAY };
859 const quint32 streamID = m_nextStreamID;
860 if (size_t(m_maxConcurrentStreams) <= size_t(numActiveLocalStreams()))
861 return { QHttp2Connection::CreateStreamError::MaxConcurrentStreamsReached };
862
863 if (QHttp2Stream *ptr = createStreamInternal_impl(streamID)) {
864 m_nextStreamID += 2;
865 return {ptr};
866 }
867 // Connection could be broken, we could've ran out of memory, we don't know
868 return { QHttp2Connection::CreateStreamError::UnknownError };
869}
870
871QHttp2Stream *QHttp2Connection::createStreamInternal_impl(quint32 streamID)
872{
873 Q_ASSERT(streamID > m_lastIncomingStreamID || streamID >= m_nextStreamID);
874
875 qsizetype numStreams = m_streams.size();
876 QPointer<QHttp2Stream> &stream = m_streams[streamID];
877 if (numStreams == m_streams.size()) // stream already existed
878 return nullptr;
879 stream = new QHttp2Stream(this, streamID);
880 stream->m_recvWindow = streamInitialReceiveWindowSize;
881 stream->m_sendWindow = streamInitialSendWindowSize;
882
883 connect(sender: stream, signal: &QHttp2Stream::uploadBlocked, context: this, slot: [this, stream] {
884 m_blockedStreams.insert(value: stream->streamID());
885 });
886 return stream;
887}
888
889qsizetype QHttp2Connection::numActiveStreamsImpl(quint32 mask) const noexcept
890{
891 const auto shouldCount = [mask](const QPointer<QHttp2Stream> &stream) -> bool {
892 return stream && (stream->streamID() & 1) == mask && stream->isActive();
893 };
894 return std::count_if(first: m_streams.cbegin(), last: m_streams.cend(), pred: shouldCount);
895}
896
897/*!
898 \internal
899 The number of streams the remote peer has started that are still active.
900*/
901qsizetype QHttp2Connection::numActiveRemoteStreams() const noexcept
902{
903 const quint32 RemoteMask = m_connectionType == Type::Client ? 0 : 1;
904 return numActiveStreamsImpl(mask: RemoteMask);
905}
906
907/*!
908 \internal
909 The number of streams we have started that are still active.
910*/
911qsizetype QHttp2Connection::numActiveLocalStreams() const noexcept
912{
913 const quint32 LocalMask = m_connectionType == Type::Client ? 1 : 0;
914 return numActiveStreamsImpl(mask: LocalMask);
915}
916
917/*!
918 Return a pointer to a stream with the given \a streamID, or null if no such
919 stream exists or it was deleted.
920*/
921QHttp2Stream *QHttp2Connection::getStream(quint32 streamID) const
922{
923 return m_streams.value(key: streamID, defaultValue: nullptr).get();
924}
925
926
927/*!
928 \fn QHttp2Stream *QHttp2Connection::promisedStream(const QUrl &streamKey) const
929
930 Returns a pointer to the stream that was promised with the given
931 \a streamKey, if any. Otherwise, returns null.
932*/
933
934/*!
935 \fn void QHttp2Connection::close()
936
937 This sends a GOAWAY frame on the connection stream, gracefully closing the
938 connection.
939*/
940
941/*!
942 \fn bool QHttp2Connection::isGoingAway() const noexcept
943
944 Returns \c true if the connection is in the process of being closed, or
945 \c false otherwise.
946*/
947
948/*!
949 \fn quint32 QHttp2Connection::maxConcurrentStreams() const noexcept
950
951 Returns the maximum number of concurrent streams we are allowed to have
952 active at any given time. This is a directional setting, and the remote
953 peer may have a different value.
954*/
955
956/*!
957 \fn quint32 QHttp2Connection::maxHeaderListSize() const noexcept
958
959 Returns the maximum size of the header which the peer is willing to accept.
960*/
961
962/*!
963 \fn bool QHttp2Connection::isUpgradedConnection() const noexcept
964
965 Returns \c true if this connection was created as a result of an HTTP/1
966 upgrade to HTTP/2, or \c false otherwise.
967*/
968
969QHttp2Connection::QHttp2Connection(QIODevice *socket) : QObject(socket)
970{
971 Q_ASSERT(socket);
972 Q_ASSERT(socket->isOpen());
973 Q_ASSERT(socket->openMode() & QIODevice::ReadWrite);
974 // We don't make any connections directly because this is used in
975 // in the http2 protocol handler, which is used by
976 // QHttpNetworkConnectionChannel. Which in turn owns and deals with all the
977 // socket connections.
978}
979
980QHttp2Connection::~QHttp2Connection() noexcept
981{
982 // delete streams now so that any calls it might make back to this
983 // Connection will operate on a valid object.
984 for (QPointer<QHttp2Stream> &stream : std::exchange(obj&: m_streams, new_val: {}))
985 delete stream.get();
986}
987
988bool QHttp2Connection::serverCheckClientPreface()
989{
990 if (!m_waitingForClientPreface)
991 return true;
992 auto *socket = getSocket();
993 if (socket->bytesAvailable() < Http2::clientPrefaceLength)
994 return false;
995 if (!readClientPreface()) {
996 socket->close();
997 emit errorOccurred(errorCode: Http2Error::PROTOCOL_ERROR, errorString: "invalid client preface"_L1);
998 qCDebug(qHttp2ConnectionLog, "[%p] Invalid client preface", this);
999 return false;
1000 }
1001 qCDebug(qHttp2ConnectionLog, "[%p] Peer sent valid client preface", this);
1002 m_waitingForClientPreface = false;
1003 if (!sendServerPreface()) {
1004 connectionError(errorCode: INTERNAL_ERROR, message: "Failed to send server preface");
1005 return false;
1006 }
1007 return true;
1008}
1009
1010bool QHttp2Connection::sendPing()
1011{
1012 std::array<char, 8> data;
1013
1014 QRandomGenerator gen;
1015 gen.generate(begin: data.begin(), end: data.end());
1016 return sendPing(data);
1017}
1018
1019bool QHttp2Connection::sendPing(QByteArrayView data)
1020{
1021 frameWriter.start(type: FrameType::PING, flags: FrameFlag::EMPTY, streamID: connectionStreamID);
1022
1023 Q_ASSERT(data.length() == 8);
1024 if (!m_lastPingSignature) {
1025 m_lastPingSignature = data.toByteArray();
1026 } else {
1027 qCWarning(qHttp2ConnectionLog, "[%p] No PING is sent while waiting for the previous PING.", this);
1028 return false;
1029 }
1030
1031 frameWriter.append(begin: (uchar*)data.data(), end: (uchar*)data.end());
1032 frameWriter.write(socket&: *getSocket());
1033 return true;
1034}
1035
1036/*!
1037 This function must be called when you have received a readyRead signal
1038 (or equivalent) from the QIODevice. It will read and process any incoming
1039 HTTP/2 frames and emit signals as appropriate.
1040*/
1041void QHttp2Connection::handleReadyRead()
1042{
1043 /* event loop */
1044 if (m_connectionType == Type::Server && !serverCheckClientPreface())
1045 return;
1046
1047 const auto streamIsActive = [](const QPointer<QHttp2Stream> &stream) {
1048 return stream && stream->isActive();
1049 };
1050 if (m_goingAway && std::none_of(first: m_streams.cbegin(), last: m_streams.cend(), pred: streamIsActive)) {
1051 close();
1052 return;
1053 }
1054 QIODevice *socket = getSocket();
1055
1056 qCDebug(qHttp2ConnectionLog, "[%p] Receiving data, %lld bytes available", this,
1057 socket->bytesAvailable());
1058
1059 using namespace Http2;
1060 while (!m_goingAway || std::any_of(first: m_streams.cbegin(), last: m_streams.cend(), pred: streamIsActive)) {
1061 const auto result = frameReader.read(socket&: *socket);
1062 if (result != FrameStatus::goodFrame)
1063 qCDebug(qHttp2ConnectionLog, "[%p] Tried to read frame, got %d", this, int(result));
1064 switch (result) {
1065 case FrameStatus::incompleteFrame:
1066 return;
1067 case FrameStatus::protocolError:
1068 return connectionError(errorCode: PROTOCOL_ERROR, message: "invalid frame");
1069 case FrameStatus::sizeError: {
1070 const auto streamID = frameReader.inboundFrame().streamID();
1071 const auto frameType = frameReader.inboundFrame().type();
1072 auto stream = getStream(streamID);
1073 // RFC 9113, 4.2: A frame size error in a frame that could alter the state of the
1074 // entire connection MUST be treated as a connection error (Section 5.4.1); this
1075 // includes any frame carrying a field block (Section 4.3) (that is, HEADERS,
1076 // PUSH_PROMISE, and CONTINUATION), a SETTINGS frame, and any frame with a stream
1077 // identifier of 0.
1078 if (frameType == FrameType::HEADERS ||
1079 frameType == FrameType::SETTINGS ||
1080 frameType == FrameType::PUSH_PROMISE ||
1081 frameType == FrameType::CONTINUATION ||
1082 // never reply RST_STREAM with RST_STREAM
1083 frameType == FrameType::RST_STREAM ||
1084 streamID == connectionStreamID)
1085 return connectionError(errorCode: FRAME_SIZE_ERROR, message: "invalid frame size");
1086 // DATA; PRIORITY; WINDOW_UPDATE
1087 if (stream)
1088 return stream->streamError(errorCode: Http2Error::FRAME_SIZE_ERROR,
1089 message: QLatin1String("invalid frame size"));
1090 else
1091 return; // most likely a closed and deleted stream. Can be ignored.
1092 }
1093 default:
1094 break;
1095 }
1096
1097 Q_ASSERT(result == FrameStatus::goodFrame);
1098
1099 inboundFrame = std::move(frameReader.inboundFrame());
1100
1101 const auto frameType = inboundFrame.type();
1102 qCDebug(qHttp2ConnectionLog, "[%p] Successfully read a frame, with type: %d", this,
1103 int(frameType));
1104
1105 // RFC 9113, 6.2/6.6: A HEADERS/PUSH_PROMISE frame without the END_HEADERS flag set MUST be
1106 // followed by a CONTINUATION frame for the same stream. A receiver MUST treat the
1107 // receipt of any other type of frame or a frame on a different stream as a
1108 // connection error
1109 if (continuationExpected && frameType != FrameType::CONTINUATION)
1110 return connectionError(errorCode: PROTOCOL_ERROR, message: "CONTINUATION expected");
1111
1112 switch (frameType) {
1113 case FrameType::DATA:
1114 handleDATA();
1115 break;
1116 case FrameType::HEADERS:
1117 handleHEADERS();
1118 break;
1119 case FrameType::PRIORITY:
1120 handlePRIORITY();
1121 break;
1122 case FrameType::RST_STREAM:
1123 handleRST_STREAM();
1124 break;
1125 case FrameType::SETTINGS:
1126 handleSETTINGS();
1127 break;
1128 case FrameType::PUSH_PROMISE:
1129 handlePUSH_PROMISE();
1130 break;
1131 case FrameType::PING:
1132 handlePING();
1133 break;
1134 case FrameType::GOAWAY:
1135 handleGOAWAY();
1136 break;
1137 case FrameType::WINDOW_UPDATE:
1138 handleWINDOW_UPDATE();
1139 break;
1140 case FrameType::CONTINUATION:
1141 handleCONTINUATION();
1142 break;
1143 case FrameType::LAST_FRAME_TYPE:
1144 // 5.1 - ignore unknown frames.
1145 break;
1146 }
1147 }
1148}
1149
1150bool QHttp2Connection::readClientPreface()
1151{
1152 auto *socket = getSocket();
1153 Q_ASSERT(socket->bytesAvailable() >= Http2::clientPrefaceLength);
1154 char buffer[Http2::clientPrefaceLength];
1155 const qint64 read = socket->read(data: buffer, maxlen: Http2::clientPrefaceLength);
1156 if (read != Http2::clientPrefaceLength)
1157 return false;
1158 return memcmp(s1: buffer, s2: Http2::Http2clientPreface, n: Http2::clientPrefaceLength) == 0;
1159}
1160
1161/*!
1162 This function must be called when the socket has been disconnected, and will
1163 end all remaining streams with an error.
1164*/
1165void QHttp2Connection::handleConnectionClosure()
1166{
1167 const auto errorString = QCoreApplication::translate(context: "QHttp", key: "Connection closed");
1168 for (auto it = m_streams.cbegin(), end = m_streams.cend(); it != end; ++it) {
1169 const QPointer<QHttp2Stream> &stream = it.value();
1170 if (stream && stream->isActive())
1171 stream->finishWithError(errorCode: PROTOCOL_ERROR, message: errorString);
1172 }
1173}
1174
1175void QHttp2Connection::setH2Configuration(QHttp2Configuration config)
1176{
1177 m_config = std::move(config);
1178
1179 // These values comes from our own API so trust it to be sane.
1180 maxSessionReceiveWindowSize = qint32(m_config.sessionReceiveWindowSize());
1181 pushPromiseEnabled = m_config.serverPushEnabled();
1182 streamInitialReceiveWindowSize = qint32(m_config.streamReceiveWindowSize());
1183 encoder.setCompressStrings(m_config.huffmanCompressionEnabled());
1184}
1185
1186void QHttp2Connection::connectionError(Http2Error errorCode, const char *message)
1187{
1188 Q_ASSERT(message);
1189 // RFC 9113, 6.8: An endpoint MAY send multiple GOAWAY frames if circumstances change.
1190 // Anyway, we do not send multiple GOAWAY frames.
1191 if (m_goingAway)
1192 return;
1193
1194 qCCritical(qHttp2ConnectionLog, "[%p] Connection error: %s (%d)", this, message,
1195 int(errorCode));
1196
1197 // RFC 9113, 6.8: Endpoints SHOULD always send a GOAWAY frame before closing a connection so
1198 // that the remote peer can know whether a stream has been partially processed or not.
1199 m_goingAway = true;
1200 sendGOAWAY(errorCode);
1201 auto messageView = QLatin1StringView(message);
1202
1203 for (QHttp2Stream *stream : std::as_const(t&: m_streams)) {
1204 if (stream && stream->isActive())
1205 stream->finishWithError(errorCode, message: messageView);
1206 }
1207
1208 closeSession();
1209}
1210
1211void QHttp2Connection::closeSession()
1212{
1213 emit connectionClosed();
1214}
1215
1216bool QHttp2Connection::streamWasResetLocally(quint32 streamID) noexcept
1217{
1218 return m_resetStreamIDs.contains(t: streamID);
1219}
1220
1221void QHttp2Connection::registerStreamAsResetLocally(quint32 streamID)
1222{
1223 // RFC 9113, 6.4: However, after sending the RST_STREAM, the sending endpoint MUST be prepared
1224 // to receive and process additional frames sent on the stream that might have been sent by the
1225 // peer prior to the arrival of the RST_STREAM.
1226
1227 // Store the last 100 stream ids that were reset locally. Frames received on these streams
1228 // are still considered valid for some time (Until 100 other streams are reset locally).
1229 m_resetStreamIDs.append(t: streamID);
1230 while (m_resetStreamIDs.size() > 100)
1231 m_resetStreamIDs.takeFirst();
1232}
1233
1234bool QHttp2Connection::isInvalidStream(quint32 streamID) noexcept
1235{
1236 auto stream = m_streams.value(key: streamID, defaultValue: nullptr);
1237 return (!stream || stream->wasResetbyPeer()) && !streamWasResetLocally(streamID);
1238}
1239
1240bool QHttp2Connection::sendClientPreface()
1241{
1242 QIODevice *socket = getSocket();
1243 // 3.5 HTTP/2 Connection Preface
1244 const qint64 written = socket->write(data: Http2clientPreface, len: clientPrefaceLength);
1245 if (written != clientPrefaceLength)
1246 return false;
1247
1248 if (!sendSETTINGS()) {
1249 qCWarning(qHttp2ConnectionLog, "[%p] Failed to send SETTINGS", this);
1250 return false;
1251 }
1252 return true;
1253}
1254
1255bool QHttp2Connection::sendServerPreface()
1256{
1257 // We send our SETTINGS frame and ACK the client's SETTINGS frame when it
1258 // arrives.
1259 if (!sendSETTINGS()) {
1260 qCWarning(qHttp2ConnectionLog, "[%p] Failed to send SETTINGS", this);
1261 return false;
1262 }
1263 return true;
1264}
1265
1266bool QHttp2Connection::sendSETTINGS()
1267{
1268 QIODevice *socket = getSocket();
1269 // 6.5 SETTINGS
1270 frameWriter.setOutboundFrame(configurationToSettingsFrame(configuration: m_config));
1271 qCDebug(qHttp2ConnectionLog, "[%p] Sending SETTINGS frame, %d bytes", this,
1272 frameWriter.outboundFrame().payloadSize());
1273 Q_ASSERT(frameWriter.outboundFrame().payloadSize());
1274
1275 if (!frameWriter.write(socket&: *socket))
1276 return false;
1277
1278 sessionReceiveWindowSize = maxSessionReceiveWindowSize;
1279 // We only send WINDOW_UPDATE for the connection if the size differs from the
1280 // default 64 KB:
1281 const auto delta = maxSessionReceiveWindowSize - defaultSessionWindowSize;
1282 if (delta && !sendWINDOW_UPDATE(streamID: connectionStreamID, delta))
1283 return false;
1284
1285 waitingForSettingsACK = true;
1286 return true;
1287}
1288
1289bool QHttp2Connection::sendWINDOW_UPDATE(quint32 streamID, quint32 delta)
1290{
1291 qCDebug(qHttp2ConnectionLog, "[%p] Sending WINDOW_UPDATE frame, stream %d, delta %u", this,
1292 streamID, delta);
1293 frameWriter.start(type: FrameType::WINDOW_UPDATE, flags: FrameFlag::EMPTY, streamID);
1294 frameWriter.append(val: delta);
1295 return frameWriter.write(socket&: *getSocket());
1296}
1297
1298bool QHttp2Connection::sendGOAWAY(Http2::Http2Error errorCode)
1299{
1300 frameWriter.start(type: FrameType::GOAWAY, flags: FrameFlag::EMPTY,
1301 streamID: Http2PredefinedParameters::connectionStreamID);
1302 frameWriter.append(val: quint32(m_lastIncomingStreamID));
1303 frameWriter.append(val: quint32(errorCode));
1304 return frameWriter.write(socket&: *getSocket());
1305}
1306
1307bool QHttp2Connection::sendSETTINGS_ACK()
1308{
1309 frameWriter.start(type: FrameType::SETTINGS, flags: FrameFlag::ACK, streamID: Http2::connectionStreamID);
1310 return frameWriter.write(socket&: *getSocket());
1311}
1312
1313void QHttp2Connection::handleDATA()
1314{
1315 Q_ASSERT(inboundFrame.type() == FrameType::DATA);
1316
1317 const auto streamID = inboundFrame.streamID();
1318
1319 // RFC9113, 6.1: An endpoint that receives an unexpected stream identifier MUST respond
1320 // with a connection error.
1321 if (streamID == connectionStreamID)
1322 return connectionError(errorCode: PROTOCOL_ERROR, message: "DATA on the connection stream");
1323
1324 if (isInvalidStream(streamID))
1325 return connectionError(errorCode: ENHANCE_YOUR_CALM, message: "DATA on invalid stream");
1326
1327 // RFC9113, 6.1: If a DATA frame is received whose stream is not in the "open" or
1328 // "half-closed (local)" state, the recipient MUST respond with a stream error.
1329 auto stream = getStream(streamID);
1330 if (stream->state() == QHttp2Stream::State::HalfClosedRemote
1331 || stream->state() == QHttp2Stream::State::Closed) {
1332 return stream->streamError(errorCode: Http2Error::STREAM_CLOSED,
1333 message: QLatin1String("Data on closed stream"));
1334 }
1335
1336 if (qint32(inboundFrame.payloadSize()) > sessionReceiveWindowSize) {
1337 qCDebug(qHttp2ConnectionLog,
1338 "[%p] Received DATA frame with payload size %u, "
1339 "but recvWindow is %d, sending FLOW_CONTROL_ERROR",
1340 this, inboundFrame.payloadSize(), sessionReceiveWindowSize);
1341 return connectionError(errorCode: FLOW_CONTROL_ERROR, message: "Flow control error");
1342 }
1343
1344 sessionReceiveWindowSize -= inboundFrame.payloadSize();
1345
1346 auto it = m_streams.constFind(key: streamID);
1347 if (it != m_streams.cend() && it.value())
1348 it.value()->handleDATA(inboundFrame);
1349
1350 if (inboundFrame.flags().testFlag(flag: FrameFlag::END_STREAM))
1351 emit receivedEND_STREAM(streamID);
1352
1353 if (sessionReceiveWindowSize < maxSessionReceiveWindowSize / 2) {
1354 // @future[consider]: emit signal instead
1355 QMetaObject::invokeMethod(object: this, function: &QHttp2Connection::sendWINDOW_UPDATE, type: Qt::QueuedConnection,
1356 args: quint32(connectionStreamID),
1357 args: quint32(maxSessionReceiveWindowSize - sessionReceiveWindowSize));
1358 sessionReceiveWindowSize = maxSessionReceiveWindowSize;
1359 }
1360}
1361
1362void QHttp2Connection::handleHEADERS()
1363{
1364 Q_ASSERT(inboundFrame.type() == FrameType::HEADERS);
1365
1366 const auto streamID = inboundFrame.streamID();
1367 qCDebug(qHttp2ConnectionLog, "[%p] Received HEADERS frame on stream %d", this, streamID);
1368
1369 // RFC 9113, 6.2: If a HEADERS frame is received whose Stream Identifier field is 0x00, the
1370 // recipient MUST respond with a connection error.
1371 if (streamID == connectionStreamID)
1372 return connectionError(errorCode: PROTOCOL_ERROR, message: "HEADERS on 0x0 stream");
1373
1374 const bool isClient = m_connectionType == Type::Client;
1375 const bool isClientInitiatedStream = !!(streamID & 1);
1376 const bool isRemotelyInitiatedStream = isClient ^ isClientInitiatedStream;
1377
1378 if (isRemotelyInitiatedStream && streamID > m_lastIncomingStreamID) {
1379 QHttp2Stream *newStream = createStreamInternal_impl(streamID);
1380 Q_ASSERT(newStream);
1381 m_lastIncomingStreamID = streamID;
1382 qCDebug(qHttp2ConnectionLog, "[%p] Created new incoming stream %d", this, streamID);
1383 emit newIncomingStream(stream: newStream);
1384 } else if (auto it = m_streams.constFind(key: streamID); it == m_streams.cend()) {
1385 // RFC 9113, 6.2: HEADERS frames MUST be associated with a stream.
1386 // A connection error is not required but it seems to be the right thing to do.
1387 qCDebug(qHttp2ConnectionLog, "[%p] Received HEADERS on non-existent stream %d", this,
1388 streamID);
1389 return connectionError(errorCode: PROTOCOL_ERROR, message: "HEADERS on invalid stream");
1390 } else if (isInvalidStream(streamID)) {
1391 // RFC 9113 6.4: After receiving a RST_STREAM on a stream, the receiver MUST NOT send
1392 // additional frames for that stream
1393 qCDebug(qHttp2ConnectionLog, "[%p] Received HEADERS on reset stream %d", this, streamID);
1394 return connectionError(errorCode: ENHANCE_YOUR_CALM, message: "HEADERS on invalid stream");
1395 }
1396
1397 const auto flags = inboundFrame.flags();
1398 if (flags.testFlag(flag: FrameFlag::PRIORITY)) {
1399 qCDebug(qHttp2ConnectionLog, "[%p] HEADERS frame on stream %d has PRIORITY flag", this,
1400 streamID);
1401 handlePRIORITY();
1402 if (m_goingAway)
1403 return;
1404 }
1405
1406 const bool endHeaders = flags.testFlag(flag: FrameFlag::END_HEADERS);
1407 continuedFrames.clear();
1408 continuedFrames.push_back(x: std::move(inboundFrame));
1409 if (!endHeaders) {
1410 continuationExpected = true;
1411 return;
1412 }
1413
1414 handleContinuedHEADERS();
1415}
1416
1417void QHttp2Connection::handlePRIORITY()
1418{
1419 Q_ASSERT(inboundFrame.type() == FrameType::PRIORITY
1420 || inboundFrame.type() == FrameType::HEADERS);
1421
1422 const auto streamID = inboundFrame.streamID();
1423 // RFC 9913, 6.3: If a PRIORITY frame is received with a stream identifier of 0x00, the
1424 // recipient MUST respond with a connection error
1425 if (streamID == connectionStreamID)
1426 return connectionError(errorCode: PROTOCOL_ERROR, message: "PRIORITY on 0x0 stream");
1427
1428 // RFC 9113 6.4: After receiving a RST_STREAM on a stream, the receiver MUST NOT send
1429 // additional frames for that stream
1430 if (isInvalidStream(streamID))
1431 return connectionError(errorCode: ENHANCE_YOUR_CALM, message: "PRIORITY on invalid stream");
1432
1433 // RFC 9913, 6.3: A PRIORITY frame with a length other than 5 octets MUST be treated as a
1434 // stream error (Section 5.4.2) of type FRAME_SIZE_ERROR.
1435 // checked in Frame::validateHeader()
1436 Q_ASSERT(inboundFrame.type() != FrameType::PRIORITY || inboundFrame.payloadSize() == 5);
1437
1438 quint32 streamDependency = 0;
1439 uchar weight = 0;
1440 const bool noErr = inboundFrame.priority(streamID: &streamDependency, weight: &weight);
1441 Q_UNUSED(noErr);
1442 Q_ASSERT(noErr);
1443
1444 const bool exclusive = streamDependency & 0x80000000;
1445 streamDependency &= ~0x80000000;
1446
1447 // Ignore this for now ...
1448 // Can be used for streams (re)prioritization - 5.3
1449 Q_UNUSED(exclusive);
1450 Q_UNUSED(weight);
1451}
1452
1453void QHttp2Connection::handleRST_STREAM()
1454{
1455 Q_ASSERT(inboundFrame.type() == FrameType::RST_STREAM);
1456
1457 // RFC 9113, 6.4: RST_STREAM frames MUST be associated with a stream.
1458 // If a RST_STREAM frame is received with a stream identifier of 0x0,
1459 // the recipient MUST treat this as a connection error (Section 5.4.1)
1460 // of type PROTOCOL_ERROR.
1461 const auto streamID = inboundFrame.streamID();
1462 if (streamID == connectionStreamID)
1463 return connectionError(errorCode: PROTOCOL_ERROR, message: "RST_STREAM on 0x0");
1464
1465 // RFC 9113, 6.4: A RST_STREAM frame with a length other than 4 octets MUST be treated as a
1466 // connection error (Section 5.4.1) of type FRAME_SIZE_ERROR.
1467 // checked in Frame::validateHeader()
1468 Q_ASSERT(inboundFrame.payloadSize() == 4);
1469
1470 const auto error = qFromBigEndian<quint32>(src: inboundFrame.dataBegin());
1471 if (QPointer<QHttp2Stream> stream = m_streams[streamID])
1472 emit stream->rstFrameRecived(errorCode: error);
1473
1474 // Verify that whatever stream is being RST'd is not in the idle state:
1475 const quint32 lastRelevantStreamID = [this, streamID]() {
1476 quint32 peerMask = m_connectionType == Type::Client ? 0 : 1;
1477 return ((streamID & 1) == peerMask) ? m_lastIncomingStreamID : m_nextStreamID - 2;
1478 }();
1479 if (streamID > lastRelevantStreamID) {
1480 // "RST_STREAM frames MUST NOT be sent for a stream
1481 // in the "idle" state. .. the recipient MUST treat this
1482 // as a connection error (Section 5.4.1) of type PROTOCOL_ERROR."
1483 return connectionError(errorCode: PROTOCOL_ERROR, message: "RST_STREAM on idle stream");
1484 }
1485
1486 Q_ASSERT(inboundFrame.dataSize() == 4);
1487
1488 if (QPointer<QHttp2Stream> stream = m_streams[streamID])
1489 stream->handleRST_STREAM(inboundFrame);
1490}
1491
1492void QHttp2Connection::handleSETTINGS()
1493{
1494 // 6.5 SETTINGS.
1495 Q_ASSERT(inboundFrame.type() == FrameType::SETTINGS);
1496
1497 // RFC 9113, 6.5: If an endpoint receives a SETTINGS frame whose Stream Identifier field is
1498 // anything other than 0x00, the endpoint MUST respond with a connection error
1499 if (inboundFrame.streamID() != connectionStreamID)
1500 return connectionError(errorCode: PROTOCOL_ERROR, message: "SETTINGS on invalid stream");
1501
1502 if (inboundFrame.flags().testFlag(flag: FrameFlag::ACK)) {
1503 // RFC 9113, 6.5: Receipt of a SETTINGS frame with the ACK flag set and a length field
1504 // value other than 0 MUST be treated as a connection error
1505 if (inboundFrame.payloadSize ())
1506 return connectionError(errorCode: FRAME_SIZE_ERROR, message: "SETTINGS ACK with data");
1507 if (!waitingForSettingsACK)
1508 return connectionError(errorCode: PROTOCOL_ERROR, message: "unexpected SETTINGS ACK");
1509 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS ACK", this);
1510 waitingForSettingsACK = false;
1511 return;
1512 }
1513 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS frame", this);
1514
1515 if (inboundFrame.dataSize()) {
1516 // RFC 9113, 6.5: A SETTINGS frame with a length other than a multiple of 6 octets MUST be
1517 // treated as a connection error (Section 5.4.1) of type FRAME_SIZE_ERROR.
1518 // checked in Frame::validateHeader()
1519 Q_ASSERT (inboundFrame.payloadSize() % 6 == 0);
1520
1521 auto src = inboundFrame.dataBegin();
1522 for (const uchar *end = src + inboundFrame.dataSize(); src != end; src += 6) {
1523 const Settings identifier = Settings(qFromBigEndian<quint16>(src));
1524 const quint32 intVal = qFromBigEndian<quint32>(src: src + 2);
1525 if (!acceptSetting(identifier, newValue: intVal)) {
1526 // If not accepted - we finish with connectionError.
1527 qCDebug(qHttp2ConnectionLog, "[%p] Received an unacceptable setting, %u, %u", this,
1528 quint32(identifier), intVal);
1529 return; // connectionError already called in acceptSetting.
1530 }
1531 }
1532 }
1533
1534 qCDebug(qHttp2ConnectionLog, "[%p] Sending SETTINGS ACK", this);
1535 emit settingsFrameReceived();
1536 sendSETTINGS_ACK();
1537}
1538
1539void QHttp2Connection::handlePUSH_PROMISE()
1540{
1541 // 6.6 PUSH_PROMISE.
1542 Q_ASSERT(inboundFrame.type() == FrameType::PUSH_PROMISE);
1543
1544 // RFC 9113, 6.6: PUSH_PROMISE MUST NOT be sent if the SETTINGS_ENABLE_PUSH setting of the peer
1545 // endpoint is set to 0. An endpoint that has set this setting and has received acknowledgment
1546 // MUST treat the receipt of a PUSH_PROMISE frame as a connection error
1547 if (!pushPromiseEnabled && !waitingForSettingsACK) {
1548 // This means, server ACKed our 'NO PUSH',
1549 // but sent us PUSH_PROMISE anyway.
1550 return connectionError(errorCode: PROTOCOL_ERROR, message: "unexpected PUSH_PROMISE frame");
1551 }
1552
1553 // RFC 9113, 6.6: If the Stream Identifier field specifies the value 0x00, a recipient MUST
1554 // respond with a connection error.
1555 const auto streamID = inboundFrame.streamID();
1556 if (streamID == connectionStreamID)
1557 return connectionError(errorCode: PROTOCOL_ERROR, message: "PUSH_PROMISE with invalid associated stream (0x0)");
1558
1559 auto it = m_streams.constFind(key: streamID);
1560#if 0 // Needs to be done after some timeout in case the stream has only just been reset
1561 if (it != m_streams.constEnd()) {
1562 QHttp2Stream *associatedStream = it->get();
1563 if (associatedStream->state() != QHttp2Stream::State::Open
1564 && associatedStream->state() != QHttp2Stream::State::HalfClosedLocal) {
1565 // Cause us to error out below:
1566 it = m_streams.constEnd();
1567 }
1568 }
1569#endif
1570 // RFC 9113, 6.6: PUSH_PROMISE frames MUST only be sent on a peer-initiated stream that
1571 // is in either the "open" or "half-closed (remote)" state.
1572
1573 // I.e. If you are the server then the client must have initiated the stream you are sending
1574 // the promise on. And since this is about _sending_ we have to invert "Remote" to "Local"
1575 // because we are receiving.
1576 if (it == m_streams.constEnd())
1577 return connectionError(errorCode: ENHANCE_YOUR_CALM, message: "PUSH_PROMISE with invalid associated stream");
1578 if ((m_connectionType == Type::Client && (streamID & 1) == 0) ||
1579 (m_connectionType == Type::Server && (streamID & 1) == 1)) {
1580 return connectionError(errorCode: ENHANCE_YOUR_CALM, message: "PUSH_PROMISE with invalid associated stream");
1581 }
1582 if ((*it)->state() != QHttp2Stream::State::Open &&
1583 (*it)->state() != QHttp2Stream::State::HalfClosedLocal) {
1584 return connectionError(errorCode: ENHANCE_YOUR_CALM, message: "PUSH_PROMISE with invalid associated stream");
1585 }
1586
1587 // RFC 9113, 6.6: The promised stream identifier MUST be a valid choice for the
1588 // next stream sent by the sender
1589 const auto reservedID = qFromBigEndian<quint32>(src: inboundFrame.dataBegin());
1590 if ((reservedID & 1) || reservedID <= m_lastIncomingStreamID || reservedID > lastValidStreamID)
1591 return connectionError(errorCode: PROTOCOL_ERROR, message: "PUSH_PROMISE with invalid promised stream ID");
1592
1593 // RFC 9113, 6.6: A receiver MUST treat the receipt of a PUSH_PROMISE that promises an
1594 // illegal stream identifier (Section 5.1.1) as a connection error
1595 auto *stream = createStreamInternal_impl(streamID: reservedID);
1596 if (!stream)
1597 return connectionError(errorCode: PROTOCOL_ERROR, message: "PUSH_PROMISE with already active stream ID");
1598 m_lastIncomingStreamID = reservedID;
1599 stream->setState(QHttp2Stream::State::ReservedRemote);
1600
1601 // "ignoring a PUSH_PROMISE frame causes the stream state to become
1602 // indeterminate" - let's send RST_STREAM frame with REFUSE_STREAM code.
1603 if (!pushPromiseEnabled) {
1604 return stream->streamError(errorCode: REFUSE_STREAM,
1605 message: QLatin1String("PUSH_PROMISE not enabled but ignored"));
1606 }
1607
1608 // RFC 9113, 6.6: The total number of padding octets is determined by the value of the Pad
1609 // Length field. If the length of the padding is the length of the frame payload or greater,
1610 // the recipient MUST treat this as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
1611 // checked in Frame::validateHeader()
1612 Q_ASSERT(inboundFrame.dataSize() > inboundFrame.padding());
1613 const bool endHeaders = inboundFrame.flags().testFlag(flag: FrameFlag::END_HEADERS);
1614 continuedFrames.clear();
1615 continuedFrames.push_back(x: std::move(inboundFrame));
1616
1617 if (!endHeaders) {
1618 continuationExpected = true;
1619 return;
1620 }
1621
1622 handleContinuedHEADERS();
1623}
1624
1625void QHttp2Connection::handlePING()
1626{
1627 Q_ASSERT(inboundFrame.type() == FrameType::PING);
1628
1629 // RFC 9113, 6.7: PING frames are not associated with any individual stream. If a PING frame is
1630 // received with a Stream Identifier field value other than 0x00, the recipient MUST respond
1631 // with a connection error
1632 if (inboundFrame.streamID() != connectionStreamID)
1633 return connectionError(errorCode: PROTOCOL_ERROR, message: "PING on invalid stream");
1634
1635 // Receipt of a PING frame with a length field value other than 8 MUST be treated
1636 // as a connection error (Section 5.4.1) of type FRAME_SIZE_ERROR.
1637 // checked in Frame::validateHeader()
1638 Q_ASSERT(inboundFrame.payloadSize() == 8);
1639
1640 if (inboundFrame.flags() & FrameFlag::ACK) {
1641 QByteArrayView pingSignature(reinterpret_cast<const char *>(inboundFrame.dataBegin()), 8);
1642 if (!m_lastPingSignature.has_value()) {
1643 emit pingFrameRecived(state: PingState::PongNoPingSent);
1644 qCWarning(qHttp2ConnectionLog, "[%p] PING with ACK received but no PING was sent.", this);
1645 } else if (pingSignature != m_lastPingSignature) {
1646 emit pingFrameRecived(state: PingState::PongSignatureChanged);
1647 qCWarning(qHttp2ConnectionLog, "[%p] PING signature does not match the last PING.", this);
1648 } else {
1649 emit pingFrameRecived(state: PingState::PongSignatureIdentical);
1650 }
1651 m_lastPingSignature.reset();
1652 return;
1653 } else {
1654 emit pingFrameRecived(state: PingState::Ping);
1655
1656 }
1657
1658
1659 frameWriter.start(type: FrameType::PING, flags: FrameFlag::ACK, streamID: connectionStreamID);
1660 frameWriter.append(begin: inboundFrame.dataBegin(), end: inboundFrame.dataBegin() + 8);
1661 frameWriter.write(socket&: *getSocket());
1662}
1663
1664void QHttp2Connection::handleGOAWAY()
1665{
1666 // 6.8 GOAWAY
1667
1668 Q_ASSERT(inboundFrame.type() == FrameType::GOAWAY);
1669 // RFC 9113, 6.8: An endpoint MUST treat a GOAWAY frame with a stream identifier
1670 // other than 0x0 as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
1671 if (inboundFrame.streamID() != connectionStreamID)
1672 return connectionError(errorCode: PROTOCOL_ERROR, message: "GOAWAY on invalid stream");
1673
1674 // RFC 9113, 6.8:
1675 // Reserved (1) + Last-Stream-ID (31) + Error Code (32) + Additional Debug Data (..)
1676 // checked in Frame::validateHeader()
1677 Q_ASSERT(inboundFrame.payloadSize() >= 8);
1678
1679 const uchar *const src = inboundFrame.dataBegin();
1680 quint32 lastStreamID = qFromBigEndian<quint32>(src);
1681 const Http2Error errorCode = Http2Error(qFromBigEndian<quint32>(src: src + 4));
1682
1683 if (!lastStreamID) {
1684 // "The last stream identifier can be set to 0 if no
1685 // streams were processed."
1686 lastStreamID = 1;
1687 } else if (!(lastStreamID & 0x1)) {
1688 // 5.1.1 - we (client) use only odd numbers as stream identifiers.
1689 return connectionError(errorCode: PROTOCOL_ERROR, message: "GOAWAY with invalid last stream ID");
1690 } else if (lastStreamID >= m_nextStreamID) {
1691 // "A server that is attempting to gracefully shut down a connection SHOULD
1692 // send an initial GOAWAY frame with the last stream identifier set to 2^31-1
1693 // and a NO_ERROR code."
1694 if (lastStreamID != lastValidStreamID || errorCode != HTTP2_NO_ERROR)
1695 return connectionError(errorCode: PROTOCOL_ERROR, message: "GOAWAY invalid stream/error code");
1696 } else {
1697 lastStreamID += 2;
1698 }
1699
1700 m_goingAway = true;
1701
1702 emit receivedGOAWAY(errorCode, lastStreamID);
1703
1704 for (quint32 id = lastStreamID; id < m_nextStreamID; id += 2) {
1705 QHttp2Stream *stream = m_streams.value(key: id, defaultValue: nullptr);
1706 if (stream && stream->isActive())
1707 stream->finishWithError(errorCode, message: "Received GOAWAY"_L1);
1708 }
1709
1710 const auto isActive = [](const QHttp2Stream *stream) { return stream && stream->isActive(); };
1711 if (std::none_of(first: m_streams.cbegin(), last: m_streams.cend(), pred: isActive))
1712 closeSession();
1713}
1714
1715void QHttp2Connection::handleWINDOW_UPDATE()
1716{
1717 Q_ASSERT(inboundFrame.type() == FrameType::WINDOW_UPDATE);
1718
1719 const quint32 delta = qFromBigEndian<quint32>(src: inboundFrame.dataBegin());
1720 // RFC 9113, 6.9: A receiver MUST treat the receipt of a WINDOW_UPDATE frame with a
1721 // flow-control window increment of 0 as a stream error (Section 5.4.2) of type PROTOCOL_ERROR;
1722 // errors on the connection flow-control window MUST be treated as a connection error
1723 const bool valid = delta && delta <= quint32(std::numeric_limits<qint32>::max());
1724 const auto streamID = inboundFrame.streamID();
1725
1726
1727 // RFC 9113, 6.9: A WINDOW_UPDATE frame with a length other than 4 octets MUST be treated
1728 // as a connection error (Section 5.4.1) of type FRAME_SIZE_ERROR.
1729 // checked in Frame::validateHeader()
1730 Q_ASSERT(inboundFrame.payloadSize() == 4);
1731
1732 qCDebug(qHttp2ConnectionLog(), "[%p] Received WINDOW_UPDATE, stream %d, delta %d", this,
1733 streamID, delta);
1734 if (streamID == connectionStreamID) {
1735 qint32 sum = 0;
1736 if (!valid || qAddOverflow(v1: sessionSendWindowSize, v2: qint32(delta), r: &sum))
1737 return connectionError(errorCode: PROTOCOL_ERROR, message: "WINDOW_UPDATE invalid delta");
1738 sessionSendWindowSize = sum;
1739
1740 // Stream may have been unblocked, so maybe try to write again:
1741 const auto blockedStreams = std::exchange(obj&: m_blockedStreams, new_val: {});
1742 for (quint32 blockedStreamID : blockedStreams) {
1743 const QPointer<QHttp2Stream> stream = m_streams.value(key: blockedStreamID);
1744 if (!stream || !stream->isActive())
1745 continue;
1746 if (stream->isUploadingDATA() && !stream->isUploadBlocked())
1747 QMetaObject::invokeMethod(object: stream, function: &QHttp2Stream::maybeResumeUpload,
1748 type: Qt::QueuedConnection);
1749 }
1750 } else {
1751 QHttp2Stream *stream = m_streams.value(key: streamID);
1752 if (!stream || !stream->isActive()) {
1753 // WINDOW_UPDATE on closed streams can be ignored.
1754 qCDebug(qHttp2ConnectionLog, "[%p] Received WINDOW_UPDATE on closed stream %d", this,
1755 streamID);
1756 return;
1757 } else if (!valid) {
1758 return stream->streamError(errorCode: PROTOCOL_ERROR,
1759 message: QLatin1String("WINDOW_UPDATE invalid delta"));
1760 }
1761 stream->handleWINDOW_UPDATE(inboundFrame);
1762 }
1763}
1764
1765void QHttp2Connection::handleCONTINUATION()
1766{
1767 Q_ASSERT(inboundFrame.type() == FrameType::CONTINUATION);
1768 if (continuedFrames.empty())
1769 return connectionError(errorCode: PROTOCOL_ERROR,
1770 message: "CONTINUATION without a preceding HEADERS or PUSH_PROMISE");
1771 if (!continuationExpected)
1772 return connectionError(errorCode: PROTOCOL_ERROR,
1773 message: "CONTINUATION after a frame with the END_HEADERS flag set");
1774
1775 if (inboundFrame.streamID() != continuedFrames.front().streamID())
1776 return connectionError(errorCode: PROTOCOL_ERROR, message: "CONTINUATION on invalid stream");
1777
1778 const bool endHeaders = inboundFrame.flags().testFlag(flag: FrameFlag::END_HEADERS);
1779 continuedFrames.push_back(x: std::move(inboundFrame));
1780
1781 if (!endHeaders)
1782 return;
1783
1784 continuationExpected = false;
1785 handleContinuedHEADERS();
1786}
1787
1788void QHttp2Connection::handleContinuedHEADERS()
1789{
1790 // 'Continued' HEADERS can be: the initial HEADERS/PUSH_PROMISE frame
1791 // with/without END_HEADERS flag set plus, if no END_HEADERS flag,
1792 // a sequence of one or more CONTINUATION frames.
1793 Q_ASSERT(!continuedFrames.empty());
1794 const auto firstFrameType = continuedFrames[0].type();
1795 Q_ASSERT(firstFrameType == FrameType::HEADERS || firstFrameType == FrameType::PUSH_PROMISE);
1796
1797 const auto streamID = continuedFrames[0].streamID();
1798
1799 const auto streamIt = m_streams.constFind(key: streamID);
1800 if (firstFrameType == FrameType::HEADERS) {
1801 if (streamIt != m_streams.cend()) {
1802 QHttp2Stream *stream = streamIt.value();
1803 if (stream->state() != QHttp2Stream::State::HalfClosedLocal
1804 && stream->state() != QHttp2Stream::State::ReservedRemote
1805 && stream->state() != QHttp2Stream::State::Idle
1806 && stream->state() != QHttp2Stream::State::Open) {
1807 // We can receive HEADERS on streams initiated by our requests
1808 // (these streams are in halfClosedLocal or open state) or
1809 // remote-reserved streams from a server's PUSH_PROMISE.
1810 return stream->streamError(errorCode: PROTOCOL_ERROR, message: "HEADERS on invalid stream"_L1);
1811 }
1812 }
1813 // Else: we cannot just ignore our peer's HEADERS frames - they change
1814 // HPACK context - even though the stream was reset; apparently the peer
1815 // has yet to see the reset.
1816 }
1817
1818 std::vector<uchar> hpackBlock(assemble_hpack_block(frames: continuedFrames));
1819 const bool hasHeaderFields = !hpackBlock.empty();
1820 if (hasHeaderFields) {
1821 HPack::BitIStream inputStream{ hpackBlock.data(), hpackBlock.data() + hpackBlock.size() };
1822 if (!decoder.decodeHeaderFields(inputStream))
1823 return connectionError(errorCode: COMPRESSION_ERROR, message: "HPACK decompression failed");
1824 } else {
1825 if (firstFrameType == FrameType::PUSH_PROMISE) {
1826 // It could be a PRIORITY sent in HEADERS - already handled by this
1827 // point in handleHEADERS. If it was PUSH_PROMISE (HTTP/2 8.2.1):
1828 // "The header fields in PUSH_PROMISE and any subsequent CONTINUATION
1829 // frames MUST be a valid and complete set of request header fields
1830 // (Section 8.1.2.3) ... If a client receives a PUSH_PROMISE that does
1831 // not include a complete and valid set of header fields or the :method
1832 // pseudo-header field identifies a method that is not safe, it MUST
1833 // respond with a stream error (Section 5.4.2) of type PROTOCOL_ERROR."
1834 if (streamIt != m_streams.cend()) {
1835 (*streamIt)->streamError(errorCode: PROTOCOL_ERROR,
1836 message: QLatin1String("PUSH_PROMISE with incomplete headers"));
1837 }
1838 return;
1839 }
1840
1841 // We got back an empty hpack block. Now let's figure out if there was an error.
1842 constexpr auto hpackBlockHasContent = [](const auto &c) { return c.hpackBlockSize() > 0; };
1843 const bool anyHpackBlock = std::any_of(first: continuedFrames.cbegin(), last: continuedFrames.cend(),
1844 pred: hpackBlockHasContent);
1845 if (anyHpackBlock) // There was hpack block data, but returned empty => it overflowed.
1846 return connectionError(errorCode: FRAME_SIZE_ERROR, message: "HEADERS frame too large");
1847 }
1848
1849 if (streamIt == m_streams.cend()) // No more processing without a stream from here on.
1850 return;
1851
1852 switch (firstFrameType) {
1853 case FrameType::HEADERS:
1854 streamIt.value()->handleHEADERS(frameFlags: continuedFrames[0].flags(), headers: decoder.decodedHeader());
1855 break;
1856 case FrameType::PUSH_PROMISE: {
1857 std::optional<QUrl> promiseKey = HPack::makePromiseKeyUrl(requestHeader: decoder.decodedHeader());
1858 if (!promiseKey)
1859 return; // invalid URL/key !
1860 if (m_promisedStreams.contains(key: *promiseKey))
1861 return; // already promised!
1862 const auto promiseID = qFromBigEndian<quint32>(src: continuedFrames[0].dataBegin());
1863 QHttp2Stream *stream = m_streams.value(key: promiseID);
1864 stream->transitionState(transition: QHttp2Stream::StateTransition::CloseLocal);
1865 stream->handleHEADERS(frameFlags: continuedFrames[0].flags(), headers: decoder.decodedHeader());
1866 emit newPromisedStream(stream); // @future[consider] add promise key as argument?
1867 m_promisedStreams.emplace(key: *promiseKey, args: promiseID);
1868 break;
1869 }
1870 default:
1871 break;
1872 }
1873}
1874
1875bool QHttp2Connection::acceptSetting(Http2::Settings identifier, quint32 newValue)
1876{
1877 switch (identifier) {
1878 case Settings::HEADER_TABLE_SIZE_ID: {
1879 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS HEADER_TABLE_SIZE %d", this, newValue);
1880 if (newValue > maxAcceptableTableSize) {
1881 connectionError(errorCode: PROTOCOL_ERROR, message: "SETTINGS invalid table size");
1882 return false;
1883 }
1884 encoder.setMaxDynamicTableSize(newValue);
1885 break;
1886 }
1887 case Settings::INITIAL_WINDOW_SIZE_ID: {
1888 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS INITIAL_WINDOW_SIZE %d", this,
1889 newValue);
1890 // For every active stream - adjust its window
1891 // (and handle possible overflows as errors).
1892 if (newValue > quint32(std::numeric_limits<qint32>::max())) {
1893 connectionError(errorCode: FLOW_CONTROL_ERROR, message: "SETTINGS invalid initial window size");
1894 return false;
1895 }
1896
1897 const qint32 delta = qint32(newValue) - streamInitialSendWindowSize;
1898 streamInitialSendWindowSize = qint32(newValue);
1899
1900 qCDebug(qHttp2ConnectionLog, "[%p] Adjusting initial window size for %zu streams by %d",
1901 this, size_t(m_streams.size()), delta);
1902 for (const QPointer<QHttp2Stream> &stream : std::as_const(t&: m_streams)) {
1903 if (!stream)
1904 continue;
1905 qint32 sum = 0;
1906 if (qAddOverflow(v1: stream->m_sendWindow, v2: delta, r: &sum)) {
1907 stream->streamError(errorCode: PROTOCOL_ERROR, message: "SETTINGS window overflow"_L1);
1908 continue;
1909 }
1910 stream->m_sendWindow = sum;
1911 if (delta > 0 && stream->isUploadingDATA() && !stream->isUploadBlocked()) {
1912 QMetaObject::invokeMethod(object: stream, function: &QHttp2Stream::maybeResumeUpload,
1913 type: Qt::QueuedConnection);
1914 }
1915 }
1916 break;
1917 }
1918 case Settings::MAX_CONCURRENT_STREAMS_ID: {
1919 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS MAX_CONCURRENT_STREAMS %d", this,
1920 newValue);
1921 m_maxConcurrentStreams = newValue;
1922 break;
1923 }
1924 case Settings::MAX_FRAME_SIZE_ID: {
1925 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS MAX_FRAME_SIZE %d", this, newValue);
1926 if (newValue < Http2::minPayloadLimit || newValue > Http2::maxPayloadSize) {
1927 connectionError(errorCode: PROTOCOL_ERROR, message: "SETTINGS max frame size is out of range");
1928 return false;
1929 }
1930 maxFrameSize = newValue;
1931 break;
1932 }
1933 case Settings::MAX_HEADER_LIST_SIZE_ID: {
1934 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS MAX_HEADER_LIST_SIZE %d", this,
1935 newValue);
1936 // We just remember this value, it can later
1937 // prevent us from sending any request (and this
1938 // will end up in request/reply error).
1939 m_maxHeaderListSize = newValue;
1940 break;
1941 }
1942 case Http2::Settings::ENABLE_PUSH_ID:
1943 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS ENABLE_PUSH %d", this, newValue);
1944 if (newValue != 0 && newValue != 1) {
1945 connectionError(errorCode: PROTOCOL_ERROR, message: "SETTINGS peer sent illegal value for ENABLE_PUSH");
1946 return false;
1947 }
1948 if (m_connectionType == Type::Client) {
1949 if (newValue == 1) {
1950 connectionError(errorCode: PROTOCOL_ERROR, message: "SETTINGS server sent ENABLE_PUSH=1");
1951 return false;
1952 }
1953 } else { // server-side
1954 pushPromiseEnabled = newValue;
1955 break;
1956 }
1957 }
1958
1959 return true;
1960}
1961
1962QT_END_NAMESPACE
1963
1964#include "moc_qhttp2connection_p.cpp"
1965

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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