Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qhttp2connection.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
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
19
20Q_LOGGING_CATEGORY(qHttp2ConnectionLog, "qt.network.http2.connection", QtCriticalMsg)
21
22using namespace Qt::StringLiterals;
23using namespace Http2;
24
36QHttp2Stream::QHttp2Stream(QHttp2Connection *connection, quint32 streamID) noexcept
37 : QObject(connection), m_streamID(streamID)
38{
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 = default;
45
199void QHttp2Stream::finishWithError(quint32 errorCode, const QString &message)
200{
201 qCDebug(qHttp2ConnectionLog, "[%p] stream %u finished with error: %ls (error code: %u)",
202 getConnection(), m_streamID, qUtf16Printable(message), errorCode);
203 transitionState(StateTransition::RST);
204 emit errorOccurred(errorCode, message);
205}
206
207void QHttp2Stream::finishWithError(quint32 errorCode)
208{
211 qt_error(errorCode, error, message);
212 finishWithError(error, message);
213}
214
223{
224 if (m_state == State::Closed || m_state == State::Idle)
225 return false;
226 qCDebug(qHttp2ConnectionLog, "[%p] sending RST_STREAM on stream %u, code: %u", getConnection(),
227 m_streamID, errorCode);
228 transitionState(StateTransition::RST);
229
230 QHttp2Connection *connection = getConnection();
231 FrameWriter &frameWriter = connection->frameWriter;
232 frameWriter.start(FrameType::RST_STREAM, FrameFlag::EMPTY, m_streamID);
233 frameWriter.append(errorCode);
234 return frameWriter.write(*connection->getSocket());
235}
236
248{
249 Q_ASSERT(!m_uploadDevice);
250 Q_ASSERT(!m_uploadByteDevice);
252 if (m_state != State::Open && m_state != State::HalfClosedRemote)
253 return;
254
257 byteDevice->setParent(this);
258 m_uploadDevice = device;
259 qCDebug(qHttp2ConnectionLog, "[%p] starting sendDATA on stream %u, of IODevice: %p",
260 getConnection(), m_streamID, device);
261 sendDATA(byteDevice, endStream);
262}
263
275{
276 Q_ASSERT(!m_uploadByteDevice);
278 if (m_state != State::Open && m_state != State::HalfClosedRemote)
279 return;
280
281 qCDebug(qHttp2ConnectionLog, "[%p] starting sendDATA on stream %u, of device: %p",
282 getConnection(), m_streamID, device);
283
284 m_uploadByteDevice = device;
285 m_endStreamAfterDATA = endStream;
286 connect(m_uploadByteDevice, &QNonContiguousByteDevice::readyRead, this,
287 &QHttp2Stream::maybeResumeUpload);
288 connect(m_uploadByteDevice, &QObject::destroyed, this, &QHttp2Stream::uploadDeviceDestroyed);
289
290 internalSendDATA();
291}
292
293void QHttp2Stream::internalSendDATA()
294{
295 Q_ASSERT(m_uploadByteDevice);
296 QHttp2Connection *connection = getConnection();
297 Q_ASSERT(connection->maxFrameSize > frameHeaderSize);
298 QIODevice *socket = connection->getSocket();
299
300 qCDebug(qHttp2ConnectionLog,
301 "[%p] stream %u, about to write to socket, current session window size: %d, stream "
302 "window size: %d, bytes available: %lld",
303 connection, m_streamID, connection->sessionSendWindowSize, m_sendWindow,
304 m_uploadByteDevice->size() - m_uploadByteDevice->pos());
305
306 qint32 remainingWindowSize = std::min<qint32>(connection->sessionSendWindowSize, m_sendWindow);
307 FrameWriter &frameWriter = connection->frameWriter;
308 qint64 totalBytesWritten = 0;
309 const auto deviceCanRead = [this, connection] {
310 // We take advantage of knowing the internals of one of the devices used.
311 // It will request X bytes to move over to the http thread if there's
312 // not enough left, so we give it a large size. It will anyway return
313 // the size it can actually provide.
314 const qint64 requestSize = connection->maxFrameSize * 10ll;
315 qint64 tmp = 0;
316 return m_uploadByteDevice->readPointer(requestSize, tmp) != nullptr && tmp > 0;
317 };
318
319 bool sentEND_STREAM = false;
320 while (remainingWindowSize && deviceCanRead()) {
322 qint32 remainingBytesInFrame = qint32(connection->maxFrameSize);
323 frameWriter.start(FrameType::DATA, FrameFlag::EMPTY, streamID());
324
325 while (remainingWindowSize && deviceCanRead() && remainingBytesInFrame) {
326 const qint32 maxToWrite = std::min(remainingWindowSize, remainingBytesInFrame);
327
328 qint64 outBytesAvail = 0;
329 const char *readPointer = m_uploadByteDevice->readPointer(maxToWrite, outBytesAvail);
330 if (!readPointer || outBytesAvail <= 0) {
331 qCDebug(qHttp2ConnectionLog,
332 "[%p] stream %u, cannot write data, device (%p) has %lld bytes available",
333 connection, m_streamID, m_uploadByteDevice, outBytesAvail);
334 break;
335 }
336 const qint32 bytesToWrite = qint32(std::min<qint64>(maxToWrite, outBytesAvail));
337 frameWriter.append(QByteArrayView(readPointer, bytesToWrite));
338 m_uploadByteDevice->advanceReadPointer(bytesToWrite);
339
340 bytesWritten += bytesToWrite;
341
342 m_sendWindow -= bytesToWrite;
343 Q_ASSERT(m_sendWindow >= 0);
344 connection->sessionSendWindowSize -= bytesToWrite;
345 Q_ASSERT(connection->sessionSendWindowSize >= 0);
346 remainingBytesInFrame -= bytesToWrite;
347 Q_ASSERT(remainingBytesInFrame >= 0);
348 remainingWindowSize -= bytesToWrite;
349 Q_ASSERT(remainingWindowSize >= 0);
350 }
351
352 qCDebug(qHttp2ConnectionLog, "[%p] stream %u, writing %u bytes to socket", connection,
353 m_streamID, bytesWritten);
354 if (!deviceCanRead() && m_uploadByteDevice->atEnd() && m_endStreamAfterDATA) {
355 sentEND_STREAM = true;
356 frameWriter.addFlag(FrameFlag::END_STREAM);
357 }
358 if (!frameWriter.write(*socket)) {
359 qCDebug(qHttp2ConnectionLog, "[%p] stream %u, failed to write to socket", connection,
360 m_streamID);
361 finishWithError(QNetworkReply::ProtocolFailure, "failed to write to socket"_L1);
362 return;
363 }
364
365 totalBytesWritten += bytesWritten;
366 }
367
368 qCDebug(qHttp2ConnectionLog,
369 "[%p] stream %u, wrote %lld bytes total, if the device is not exhausted, we'll write "
370 "more later. Remaining window size: %d",
371 connection, m_streamID, totalBytesWritten, remainingWindowSize);
372
373 emit bytesWritten(totalBytesWritten);
374 if (sentEND_STREAM || (!deviceCanRead() && m_uploadByteDevice->atEnd())) {
375 qCDebug(qHttp2ConnectionLog,
376 "[%p] stream %u, exhausted device %p, sent END_STREAM? %d, %ssending end stream "
377 "after DATA",
378 connection, m_streamID, m_uploadByteDevice, sentEND_STREAM,
379 m_endStreamAfterDATA ? "" : "not ");
380 if (!sentEND_STREAM && m_endStreamAfterDATA) {
381 // We need to send an empty DATA frame with END_STREAM since we
382 // have exhausted the device, but we haven't sent END_STREAM yet.
383 // This can happen if we got a final readyRead to signify no more
384 // data available, but we hadn't sent the END_STREAM flag yet.
385 frameWriter.start(FrameType::DATA, FrameFlag::END_STREAM, streamID());
386 frameWriter.write(*socket);
387 }
388 finishSendDATA();
389 } else if (isUploadBlocked()) {
390 qCDebug(qHttp2ConnectionLog, "[%p] stream %u, upload blocked", connection, m_streamID);
392 }
393}
394
395void QHttp2Stream::finishSendDATA()
396{
397 if (m_endStreamAfterDATA)
398 transitionState(StateTransition::CloseLocal);
399
400 disconnect(m_uploadByteDevice, nullptr, this, nullptr);
401 m_uploadDevice = nullptr;
402 m_uploadByteDevice = nullptr;
404}
405
406void QHttp2Stream::maybeResumeUpload()
407{
408 qCDebug(qHttp2ConnectionLog,
409 "[%p] stream %u, maybeResumeUpload. Upload device: %p, bytes available: %lld, blocked? "
410 "%d",
411 getConnection(), m_streamID, m_uploadByteDevice,
412 !m_uploadByteDevice ? 0 : m_uploadByteDevice->size() - m_uploadByteDevice->pos(),
415 internalSendDATA();
416}
417
422bool QHttp2Stream::isUploadBlocked() const noexcept
423{
424 constexpr auto MinFrameSize = Http2::frameHeaderSize + 1; // 1 byte payload
425 return isUploadingDATA()
426 && (m_sendWindow <= MinFrameSize
427 || getConnection()->sessionSendWindowSize <= MinFrameSize);
428}
429
430void QHttp2Stream::uploadDeviceReadChannelFinished()
431{
432 maybeResumeUpload();
433}
434
442bool QHttp2Stream::sendHEADERS(const HPack::HttpHeader &headers, bool endStream, quint8 priority)
443{
444 using namespace HPack;
445 if (auto hs = header_size(headers);
446 !hs.first || hs.second > getConnection()->maxHeaderListSize()) {
447 return false;
448 }
449
450 transitionState(StateTransition::Open);
451
452 Q_ASSERT(m_state == State::Open || m_state == State::HalfClosedRemote);
453
454 QHttp2Connection *connection = getConnection();
455
456 qCDebug(qHttp2ConnectionLog, "[%p] stream %u, sending HEADERS frame with %u entries",
457 connection, streamID(), uint(headers.size()));
458
459 QIODevice *socket = connection->getSocket();
460 FrameWriter &frameWriter = connection->frameWriter;
461
462 frameWriter.start(FrameType::HEADERS, FrameFlag::PRIORITY | FrameFlag::END_HEADERS, streamID());
463 if (endStream)
464 frameWriter.addFlag(FrameFlag::END_STREAM);
465
466 frameWriter.append(quint32()); // No stream dependency in Qt.
467 frameWriter.append(priority);
468
469 // Compress in-place:
470 BitOStream outputStream(frameWriter.outboundFrame().buffer);
471 if (connection->m_connectionType == QHttp2Connection::Type::Client) {
472 if (!connection->encoder.encodeRequest(outputStream, headers))
473 return false;
474 } else {
475 if (!connection->encoder.encodeResponse(outputStream, headers))
476 return false;
477 }
478
479 bool result = frameWriter.writeHEADERS(*socket, connection->maxFrameSize);
480 if (endStream)
481 transitionState(StateTransition::CloseLocal);
482
483 return result;
484}
485
492{
493 QHttp2Connection *connection = getConnection();
494 m_recvWindow += qint32(delta);
495 connection->sendWINDOW_UPDATE(streamID(), delta);
496}
497
498void QHttp2Stream::uploadDeviceDestroyed()
499{
500 if (isUploadingDATA()) {
501 // We're in the middle of sending DATA frames, we need to abort
502 // the stream.
504 emit uploadDeviceError("Upload device destroyed while uploading"_L1);
505 }
506 m_uploadDevice = nullptr;
507}
508
509void QHttp2Stream::setState(State newState)
510{
511 if (m_state == newState)
512 return;
513 qCDebug(qHttp2ConnectionLog, "[%p] stream %u, state changed from %d to %d", getConnection(),
514 streamID(), int(m_state), int(newState));
515 m_state = newState;
517}
518
519// Changes the state as appropriate given the current state and the transition.
520// Always call this before emitting any signals since the recipient might rely
521// on the new state!
522void QHttp2Stream::transitionState(StateTransition transition)
523{
524 switch (m_state) {
525 case State::Idle:
526 if (transition == StateTransition::Open)
527 setState(State::Open);
528 else
529 Q_UNREACHABLE(); // We should transition to Open before ever getting here
530 break;
531 case State::Open:
532 switch (transition) {
533 case StateTransition::CloseLocal:
534 setState(State::HalfClosedLocal);
535 break;
536 case StateTransition::CloseRemote:
537 setState(State::HalfClosedRemote);
538 break;
539 case StateTransition::RST:
540 setState(State::Closed);
541 break;
542 case StateTransition::Open: // no-op
543 break;
544 }
545 break;
547 if (transition == StateTransition::CloseRemote || transition == StateTransition::RST)
548 setState(State::Closed);
549 break;
551 if (transition == StateTransition::CloseLocal || transition == StateTransition::RST)
552 setState(State::Closed);
553 break;
555 if (transition == StateTransition::RST) {
556 setState(State::Closed);
557 } else if (transition == StateTransition::CloseLocal) { // Receiving HEADER closes local
558 setState(State::HalfClosedLocal);
559 }
560 break;
561 case State::Closed:
562 break;
563 }
564}
565
566void QHttp2Stream::handleDATA(const Frame &inboundFrame)
567{
568 QHttp2Connection *connection = getConnection();
569
570 qCDebug(qHttp2ConnectionLog, "[%p] stream %u, received DATA frame with payload of %u bytes",
571 connection, m_streamID, inboundFrame.payloadSize());
572
573 if (qint32(inboundFrame.payloadSize()) > m_recvWindow) {
574 qCDebug(qHttp2ConnectionLog,
575 "[%p] stream %u, received DATA frame with payload size %u, "
576 "but recvWindow is %d, sending FLOW_CONTROL_ERROR",
577 connection, m_streamID, inboundFrame.payloadSize(), m_recvWindow);
578 finishWithError(QNetworkReply::ProtocolFailure, "flow control error"_L1);
580 return;
581 }
582 m_recvWindow -= qint32(inboundFrame.payloadSize());
583 const bool endStream = inboundFrame.flags().testFlag(FrameFlag::END_STREAM);
584 // Uncompress data if needed and append it ...
585 if (inboundFrame.dataSize() > 0 || endStream) {
586 QByteArray fragment(reinterpret_cast<const char *>(inboundFrame.dataBegin()),
587 inboundFrame.dataSize());
588 if (endStream)
589 transitionState(StateTransition::CloseRemote);
590 emit dataReceived(fragment, endStream);
591 m_downloadBuffer.append(std::move(fragment));
592 }
593
594 if (!endStream && m_recvWindow < connection->streamInitialReceiveWindowSize / 2) {
595 // @future[consider]: emit signal instead
596 sendWINDOW_UPDATE(quint32(connection->streamInitialReceiveWindowSize - m_recvWindow));
597 }
598}
599
600void QHttp2Stream::handleHEADERS(Http2::FrameFlags frameFlags, const HPack::HttpHeader &headers)
601{
602 if (m_state == State::Idle)
603 transitionState(StateTransition::Open);
604 const bool endStream = frameFlags.testFlag(FrameFlag::END_STREAM);
605 if (endStream)
606 transitionState(StateTransition::CloseRemote);
607 if (!headers.empty()) {
608 m_headers.insert(m_headers.end(), headers.begin(), headers.end());
610 }
611 emit headersReceived(headers, endStream);
612}
613
614void QHttp2Stream::handleRST_STREAM(const Frame &inboundFrame)
615{
616 transitionState(StateTransition::RST);
617 m_RST_STREAM_code = qFromBigEndian<quint32>(inboundFrame.dataBegin());
618 if (isUploadingDATA()) {
619 disconnect(m_uploadByteDevice, nullptr, this, nullptr);
620 m_uploadDevice = nullptr;
621 m_uploadByteDevice = nullptr;
622 }
623 finishWithError(*m_RST_STREAM_code, ""_L1);
624}
625
626void QHttp2Stream::handleWINDOW_UPDATE(const Frame &inboundFrame)
627{
628 const quint32 delta = qFromBigEndian<quint32>(inboundFrame.dataBegin());
629 const bool valid = delta && delta <= quint32(std::numeric_limits<qint32>::max());
630 qint32 sum = 0;
631 if (!valid || qAddOverflow(m_sendWindow, qint32(delta), &sum)) {
632 qCDebug(qHttp2ConnectionLog,
633 "[%p] stream %u, received WINDOW_UPDATE frame with invalid delta %u, sending "
634 "PROTOCOL_ERROR",
635 getConnection(), m_streamID, delta);
636 finishWithError(QNetworkReply::ProtocolFailure, "invalid WINDOW_UPDATE delta"_L1);
638 return;
639 }
640 m_sendWindow = sum;
641 // Stream may have been unblocked, so maybe try to write again
642 if (isUploadingDATA())
643 maybeResumeUpload();
644}
645
722{
724
725 auto connection = std::unique_ptr<QHttp2Connection>(new QHttp2Connection(socket));
726 connection->setH2Configuration(config);
727 connection->m_connectionType = QHttp2Connection::Type::Client;
728 // HTTP2 connection is already established and request was sent, so stream 1
729 // is already 'active' and is closed for any further outgoing data.
730 QHttp2Stream *stream = connection->createStreamInternal().unwrap();
731 Q_ASSERT(stream->streamID() == 1);
733 connection->m_upgradedConnection = true;
734
735 if (!connection->sendClientPreface()) {
736 qCWarning(qHttp2ConnectionLog, "[%p] Failed to send client preface", connection.get());
737 return nullptr;
738 }
739
740 return connection.release();
741}
742
751{
752 auto connection = std::unique_ptr<QHttp2Connection>(new QHttp2Connection(socket));
753 connection->setH2Configuration(config);
754 connection->m_connectionType = QHttp2Connection::Type::Client;
755
756 if (!connection->sendClientPreface()) {
757 qCWarning(qHttp2ConnectionLog, "[%p] Failed to send client preface", connection.get());
758 return nullptr;
759 }
760
761 return connection.release();
762}
763
771{
772 auto connection = std::unique_ptr<QHttp2Connection>(new QHttp2Connection(socket));
773 connection->setH2Configuration(config);
774 connection->m_connectionType = QHttp2Connection::Type::Server;
775
776 connection->m_nextStreamID = 2; // server-initiated streams must be even
777
778 connection->m_waitingForClientPreface = true;
779
780 return connection.release();
781}
782
791QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError> QHttp2Connection::createStream()
792{
793 Q_ASSERT(m_connectionType == Type::Client); // This overload is just for clients
794 if (m_nextStreamID > lastValidStreamID)
796 return createStreamInternal();
797}
798
799QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError>
800QHttp2Connection::createStreamInternal()
801{
802 if (m_goingAway)
804 const quint32 streamID = m_nextStreamID;
805 if (size_t(m_maxConcurrentStreams) <= size_t(numActiveLocalStreams()))
807 m_nextStreamID += 2;
808 return { createStreamInternal_impl(streamID) };
809}
810
811QHttp2Stream *QHttp2Connection::createStreamInternal_impl(quint32 streamID)
812{
813 qsizetype numStreams = m_streams.size();
814 QPointer<QHttp2Stream> &stream = m_streams[streamID];
815 if (numStreams == m_streams.size()) // stream already existed
816 return nullptr;
817 stream = new QHttp2Stream(this, streamID);
818 stream->m_recvWindow = streamInitialReceiveWindowSize;
819 stream->m_sendWindow = streamInitialSendWindowSize;
820 return stream;
821}
822
823qsizetype QHttp2Connection::numActiveStreamsImpl(quint32 mask) const noexcept
824{
825 const auto shouldCount = [mask](const QPointer<QHttp2Stream> &stream) -> bool {
826 return stream && (stream->streamID() & 1) == mask;
827 };
828 return std::count_if(m_streams.cbegin(), m_streams.cend(), shouldCount);
829}
830
835qsizetype QHttp2Connection::numActiveRemoteStreams() const noexcept
836{
837 const quint32 RemoteMask = m_connectionType == Type::Client ? 0 : 1;
838 return numActiveStreamsImpl(RemoteMask);
839}
840
845qsizetype QHttp2Connection::numActiveLocalStreams() const noexcept
846{
847 const quint32 LocalMask = m_connectionType == Type::Client ? 1 : 0;
848 return numActiveStreamsImpl(LocalMask);
849}
850
856{
857 return m_streams.value(streamID, nullptr).get();
858}
859
860
903QHttp2Connection::QHttp2Connection(QIODevice *socket) : QObject(socket)
904{
908 // We don't make any connections directly because this is used in
909 // in the http2 protocol handler, which is used by
910 // QHttpNetworkConnectionChannel. Which in turn owns and deals with all the
911 // socket connections.
912}
913
915{
916 // delete streams now so that any calls it might make back to this
917 // Connection will operate on a valid object.
918 for (QPointer<QHttp2Stream> &stream : std::exchange(m_streams, {}))
919 delete stream.get();
920}
921
922bool QHttp2Connection::serverCheckClientPreface()
923{
924 if (!m_waitingForClientPreface)
925 return true;
926 auto *socket = getSocket();
928 return false;
929 if (!readClientPreface()) {
930 socket->close();
931 emit errorOccurred(Http2Error::PROTOCOL_ERROR, "invalid client preface"_L1);
932 qCDebug(qHttp2ConnectionLog, "[%p] Invalid client preface", this);
933 return false;
934 }
935 qCDebug(qHttp2ConnectionLog, "[%p] Peer sent valid client preface", this);
936 m_waitingForClientPreface = false;
937 if (!sendServerPreface()) {
938 connectionError(Http2::INTERNAL_ERROR, "Failed to send server preface");
939 return false;
940 }
941 return true;
942}
943
945{
946 std::array<char, 8> data;
947
949 gen.generate(data.begin(), data.end());
950 return sendPing(data);
951}
952
954{
955 frameWriter.start(FrameType::PING, FrameFlag::EMPTY, connectionStreamID);
956
957 Q_ASSERT(data.length() == 8);
958 if (!m_lastPingSignature) {
959 m_lastPingSignature = data.toByteArray();
960 } else {
961 qCWarning(qHttp2ConnectionLog, "[%p] No PING is sent while waiting for the previous PING.", this);
962 return false;
963 }
964
965 frameWriter.append((uchar*)data.data(), (uchar*)data.end());
966 frameWriter.write(*getSocket());
967 return true;
968}
969
976{
977 /* event loop */
978 if (m_connectionType == Type::Server && !serverCheckClientPreface())
979 return;
980
981 const auto streamIsActive = [](const QPointer<QHttp2Stream> &stream) {
982 return stream && stream->isActive();
983 };
984 if (m_goingAway && std::none_of(m_streams.cbegin(), m_streams.cend(), streamIsActive)) {
985 close();
986 return;
987 }
988 QIODevice *socket = getSocket();
989
990 qCDebug(qHttp2ConnectionLog, "[%p] Receiving data, %lld bytes available", this,
992
993 using namespace Http2;
994 while (!m_goingAway || std::any_of(m_streams.cbegin(), m_streams.cend(), streamIsActive)) {
995 const auto result = frameReader.read(*socket);
996 if (result != FrameStatus::goodFrame)
997 qCDebug(qHttp2ConnectionLog, "[%p] Tried to read frame, got %d", this, int(result));
998 switch (result) {
999 case FrameStatus::incompleteFrame:
1000 return;
1001 case FrameStatus::protocolError:
1002 return connectionError(PROTOCOL_ERROR, "invalid frame");
1003 case FrameStatus::sizeError:
1004 return connectionError(FRAME_SIZE_ERROR, "invalid frame size");
1005 default:
1006 break;
1007 }
1008
1009 Q_ASSERT(result == FrameStatus::goodFrame);
1010
1011 inboundFrame = std::move(frameReader.inboundFrame());
1012
1013 const auto frameType = inboundFrame.type();
1014 qCDebug(qHttp2ConnectionLog, "[%p] Successfully read a frame, with type: %d", this,
1015 int(frameType));
1016 if (continuationExpected && frameType != FrameType::CONTINUATION)
1017 return connectionError(PROTOCOL_ERROR, "CONTINUATION expected");
1018
1019 switch (frameType) {
1020 case FrameType::DATA:
1021 handleDATA();
1022 break;
1023 case FrameType::HEADERS:
1024 handleHEADERS();
1025 break;
1026 case FrameType::PRIORITY:
1027 handlePRIORITY();
1028 break;
1029 case FrameType::RST_STREAM:
1030 handleRST_STREAM();
1031 break;
1032 case FrameType::SETTINGS:
1033 handleSETTINGS();
1034 break;
1035 case FrameType::PUSH_PROMISE:
1036 handlePUSH_PROMISE();
1037 break;
1038 case FrameType::PING:
1039 handlePING();
1040 break;
1041 case FrameType::GOAWAY:
1042 handleGOAWAY();
1043 break;
1044 case FrameType::WINDOW_UPDATE:
1045 handleWINDOW_UPDATE();
1046 break;
1047 case FrameType::CONTINUATION:
1048 handleCONTINUATION();
1049 break;
1050 case FrameType::LAST_FRAME_TYPE:
1051 // 5.1 - ignore unknown frames.
1052 break;
1053 }
1054 }
1055}
1056
1057bool QHttp2Connection::readClientPreface()
1058{
1059 auto *socket = getSocket();
1064 return false;
1066}
1067
1073{
1074 const auto errorString = QCoreApplication::translate("QHttp", "Connection closed");
1075 for (auto it = m_streams.begin(), end = m_streams.end(); it != end; ++it) {
1076 auto stream = it.value();
1077 if (stream && stream->isActive())
1078 stream->finishWithError(QNetworkReply::RemoteHostClosedError, errorString);
1079 }
1080}
1081
1082void QHttp2Connection::setH2Configuration(QHttp2Configuration config)
1083{
1084 m_config = std::move(config);
1085
1086 // These values comes from our own API so trust it to be sane.
1087 maxSessionReceiveWindowSize = qint32(m_config.sessionReceiveWindowSize());
1088 pushPromiseEnabled = m_config.serverPushEnabled();
1089 streamInitialReceiveWindowSize = qint32(m_config.streamReceiveWindowSize());
1091}
1092
1093void QHttp2Connection::connectionError(Http2Error errorCode, const char *message)
1094{
1096 if (m_goingAway)
1097 return;
1098
1099 qCCritical(qHttp2ConnectionLog, "[%p] Connection error: %s (%d)", this, message,
1100 int(errorCode));
1101
1102 m_goingAway = true;
1103 sendGOAWAY(errorCode);
1104 const auto error = qt_error(errorCode);
1105 auto messageView = QLatin1StringView(message);
1106
1107 for (QHttp2Stream *stream : std::as_const(m_streams)) {
1108 if (stream && stream->isActive())
1109 stream->finishWithError(error, messageView);
1110 }
1111
1112 closeSession();
1113}
1114
1115void QHttp2Connection::closeSession()
1116{
1118}
1119
1120bool QHttp2Connection::streamWasReset(quint32 streamID) noexcept
1121{
1122 return m_resetStreamIDs.contains(streamID);
1123}
1124
1125bool QHttp2Connection::isInvalidStream(quint32 streamID) noexcept
1126{
1127 auto stream = m_streams.value(streamID, nullptr);
1128 return !stream && !streamWasReset(streamID);
1129}
1130
1131bool QHttp2Connection::sendClientPreface()
1132{
1133 QIODevice *socket = getSocket();
1134 // 3.5 HTTP/2 Connection Preface
1136 if (written != clientPrefaceLength)
1137 return false;
1138
1139 if (!sendSETTINGS()) {
1140 qCWarning(qHttp2ConnectionLog, "[%p] Failed to send SETTINGS", this);
1141 return false;
1142 }
1143 return true;
1144}
1145
1146bool QHttp2Connection::sendServerPreface()
1147{
1148 // We send our SETTINGS frame and ACK the client's SETTINGS frame when it
1149 // arrives.
1150 if (!sendSETTINGS()) {
1151 qCWarning(qHttp2ConnectionLog, "[%p] Failed to send SETTINGS", this);
1152 return false;
1153 }
1154 return true;
1155}
1156
1157bool QHttp2Connection::sendSETTINGS()
1158{
1159 QIODevice *socket = getSocket();
1160 // 6.5 SETTINGS
1161 frameWriter.setOutboundFrame(configurationToSettingsFrame(m_config));
1162 qCDebug(qHttp2ConnectionLog, "[%p] Sending SETTINGS frame, %d bytes", this,
1163 frameWriter.outboundFrame().payloadSize());
1164 Q_ASSERT(frameWriter.outboundFrame().payloadSize());
1165
1166 if (!frameWriter.write(*socket))
1167 return false;
1168
1169 sessionReceiveWindowSize = maxSessionReceiveWindowSize;
1170 // We only send WINDOW_UPDATE for the connection if the size differs from the
1171 // default 64 KB:
1172 const auto delta = maxSessionReceiveWindowSize - defaultSessionWindowSize;
1173 if (delta && !sendWINDOW_UPDATE(connectionStreamID, delta))
1174 return false;
1175
1176 waitingForSettingsACK = true;
1177 return true;
1178}
1179
1180bool QHttp2Connection::sendWINDOW_UPDATE(quint32 streamID, quint32 delta)
1181{
1182 qCDebug(qHttp2ConnectionLog, "[%p] Sending WINDOW_UPDATE frame, stream %d, delta %u", this,
1183 streamID, delta);
1184 frameWriter.start(FrameType::WINDOW_UPDATE, FrameFlag::EMPTY, streamID);
1185 frameWriter.append(delta);
1186 return frameWriter.write(*getSocket());
1187}
1188
1189bool QHttp2Connection::sendGOAWAY(quint32 errorCode)
1190{
1191 frameWriter.start(FrameType::GOAWAY, FrameFlag::EMPTY,
1192 Http2PredefinedParameters::connectionStreamID);
1193 frameWriter.append(quint32(m_lastIncomingStreamID));
1194 frameWriter.append(errorCode);
1195 return frameWriter.write(*getSocket());
1196}
1197
1198bool QHttp2Connection::sendSETTINGS_ACK()
1199{
1200 frameWriter.start(FrameType::SETTINGS, FrameFlag::ACK, Http2::connectionStreamID);
1201 return frameWriter.write(*getSocket());
1202}
1203
1204void QHttp2Connection::handleDATA()
1205{
1206 Q_ASSERT(inboundFrame.type() == FrameType::DATA);
1207
1208 const auto streamID = inboundFrame.streamID();
1209 if (streamID == connectionStreamID)
1210 return connectionError(PROTOCOL_ERROR, "DATA on the connection stream");
1211
1212 if (isInvalidStream(streamID))
1213 return connectionError(ENHANCE_YOUR_CALM, "DATA on invalid stream");
1214
1215 if (qint32(inboundFrame.payloadSize()) > sessionReceiveWindowSize) {
1216 qCDebug(qHttp2ConnectionLog,
1217 "[%p] Received DATA frame with payload size %u, "
1218 "but recvWindow is %d, sending FLOW_CONTROL_ERROR",
1219 this, inboundFrame.payloadSize(), sessionReceiveWindowSize);
1220 return connectionError(FLOW_CONTROL_ERROR, "Flow control error");
1221 }
1222
1223 sessionReceiveWindowSize -= inboundFrame.payloadSize();
1224
1225 auto it = m_streams.constFind(streamID);
1226 if (it != m_streams.cend() && it.value())
1227 it.value()->handleDATA(inboundFrame);
1228
1229 if (sessionReceiveWindowSize < maxSessionReceiveWindowSize / 2) {
1230 // @future[consider]: emit signal instead
1231 QMetaObject::invokeMethod(this, &QHttp2Connection::sendWINDOW_UPDATE, Qt::QueuedConnection,
1233 quint32(maxSessionReceiveWindowSize - sessionReceiveWindowSize));
1234 sessionReceiveWindowSize = maxSessionReceiveWindowSize;
1235 }
1236}
1237
1238void QHttp2Connection::handleHEADERS()
1239{
1240 Q_ASSERT(inboundFrame.type() == FrameType::HEADERS);
1241
1242 const auto streamID = inboundFrame.streamID();
1243 qCDebug(qHttp2ConnectionLog, "[%p] Received HEADERS frame on stream %d", this, streamID);
1244
1245 if (streamID == connectionStreamID)
1246 return connectionError(PROTOCOL_ERROR, "HEADERS on 0x0 stream");
1247
1248 const bool isClient = m_connectionType == Type::Client;
1249 const bool isClientInitiatedStream = !!(streamID & 1);
1250 const bool isRemotelyInitiatedStream = isClient ^ isClientInitiatedStream;
1251
1252 if (isRemotelyInitiatedStream && streamID > m_lastIncomingStreamID) {
1253 QHttp2Stream *newStream = createStreamInternal_impl(streamID);
1254 Q_ASSERT(newStream);
1255 m_lastIncomingStreamID = streamID;
1256 qCDebug(qHttp2ConnectionLog, "[%p] Created new incoming stream %d", this, streamID);
1257 emit newIncomingStream(newStream);
1258 } else if (auto it = m_streams.constFind(streamID); it == m_streams.cend()) {
1259 qCDebug(qHttp2ConnectionLog, "[%p] Received HEADERS on non-existent stream %d", this,
1260 streamID);
1261 return connectionError(PROTOCOL_ERROR, "HEADERS on invalid stream");
1262 } else if (!*it || (*it)->wasReset()) {
1263 qCDebug(qHttp2ConnectionLog, "[%p] Received HEADERS on reset stream %d", this, streamID);
1264 return connectionError(ENHANCE_YOUR_CALM, "HEADERS on invalid stream");
1265 }
1266
1267 const auto flags = inboundFrame.flags();
1268 if (flags.testFlag(FrameFlag::PRIORITY)) {
1269 qCDebug(qHttp2ConnectionLog, "[%p] HEADERS frame on stream %d has PRIORITY flag", this,
1270 streamID);
1271 handlePRIORITY();
1272 if (m_goingAway)
1273 return;
1274 }
1275
1276 const bool endHeaders = flags.testFlag(FrameFlag::END_HEADERS);
1277 continuedFrames.clear();
1278 continuedFrames.push_back(std::move(inboundFrame));
1279 if (!endHeaders) {
1280 continuationExpected = true;
1281 return;
1282 }
1283
1284 handleContinuedHEADERS();
1285}
1286
1287void QHttp2Connection::handlePRIORITY()
1288{
1289 Q_ASSERT(inboundFrame.type() == FrameType::PRIORITY
1290 || inboundFrame.type() == FrameType::HEADERS);
1291
1292 const auto streamID = inboundFrame.streamID();
1293 if (streamID == connectionStreamID)
1294 return connectionError(PROTOCOL_ERROR, "PRIORITY on 0x0 stream");
1295
1296 if (isInvalidStream(streamID))
1297 return connectionError(ENHANCE_YOUR_CALM, "PRIORITY on invalid stream");
1298
1299 quint32 streamDependency = 0;
1300 uchar weight = 0;
1301 const bool noErr = inboundFrame.priority(&streamDependency, &weight);
1302 Q_UNUSED(noErr);
1303 Q_ASSERT(noErr);
1304
1305 const bool exclusive = streamDependency & 0x80000000;
1306 streamDependency &= ~0x80000000;
1307
1308 // Ignore this for now ...
1309 // Can be used for streams (re)prioritization - 5.3
1310 Q_UNUSED(exclusive);
1312}
1313
1314void QHttp2Connection::handleRST_STREAM()
1315{
1316 Q_ASSERT(inboundFrame.type() == FrameType::RST_STREAM);
1317
1318 // "RST_STREAM frames MUST be associated with a stream.
1319 // If a RST_STREAM frame is received with a stream identifier of 0x0,
1320 // the recipient MUST treat this as a connection error (Section 5.4.1)
1321 // of type PROTOCOL_ERROR.
1322 const auto streamID = inboundFrame.streamID();
1323 if (streamID == connectionStreamID)
1324 return connectionError(PROTOCOL_ERROR, "RST_STREAM on 0x0");
1325
1326 if (!(streamID & 0x1)) { // @future[server]: must be updated for server-side handling
1327 // RST_STREAM on a promised stream:
1328 // since we do not keep track of such streams,
1329 // just ignore.
1330 return;
1331 }
1332
1333 // Anything greater than m_nextStreamID has not been started yet.
1334 if (streamID >= m_nextStreamID) {
1335 // "RST_STREAM frames MUST NOT be sent for a stream
1336 // in the "idle" state. .. the recipient MUST treat this
1337 // as a connection error (Section 5.4.1) of type PROTOCOL_ERROR."
1338 return connectionError(PROTOCOL_ERROR, "RST_STREAM on idle stream");
1339 }
1340
1341 Q_ASSERT(inboundFrame.dataSize() == 4);
1342
1343 if (QPointer<QHttp2Stream> stream = m_streams[streamID])
1344 stream->handleRST_STREAM(inboundFrame);
1345}
1346
1347void QHttp2Connection::handleSETTINGS()
1348{
1349 // 6.5 SETTINGS.
1350 Q_ASSERT(inboundFrame.type() == FrameType::SETTINGS);
1351
1352 if (inboundFrame.streamID() != connectionStreamID)
1353 return connectionError(PROTOCOL_ERROR, "SETTINGS on invalid stream");
1354
1355 if (inboundFrame.flags().testFlag(FrameFlag::ACK)) {
1356 if (!waitingForSettingsACK)
1357 return connectionError(PROTOCOL_ERROR, "unexpected SETTINGS ACK");
1358 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS ACK", this);
1359 waitingForSettingsACK = false;
1360 return;
1361 }
1362 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS frame", this);
1363
1364 if (inboundFrame.dataSize()) {
1365 auto src = inboundFrame.dataBegin();
1366 for (const uchar *end = src + inboundFrame.dataSize(); src != end; src += 6) {
1367 const Settings identifier = Settings(qFromBigEndian<quint16>(src));
1368 const quint32 intVal = qFromBigEndian<quint32>(src + 2);
1369 if (!acceptSetting(identifier, intVal)) {
1370 // If not accepted - we finish with connectionError.
1371 qCDebug(qHttp2ConnectionLog, "[%p] Received an unacceptable setting, %u, %u", this,
1372 quint32(identifier), intVal);
1373 return; // connectionError already called in acceptSetting.
1374 }
1375 }
1376 }
1377
1378 qCDebug(qHttp2ConnectionLog, "[%p] Sending SETTINGS ACK", this);
1380 sendSETTINGS_ACK();
1381}
1382
1383void QHttp2Connection::handlePUSH_PROMISE()
1384{
1385 // 6.6 PUSH_PROMISE.
1386 Q_ASSERT(inboundFrame.type() == FrameType::PUSH_PROMISE);
1387
1388 if (!pushPromiseEnabled && !waitingForSettingsACK) {
1389 // This means, server ACKed our 'NO PUSH',
1390 // but sent us PUSH_PROMISE anyway.
1391 return connectionError(PROTOCOL_ERROR, "unexpected PUSH_PROMISE frame");
1392 }
1393
1394 const auto streamID = inboundFrame.streamID();
1395 if (streamID == connectionStreamID)
1396 return connectionError(PROTOCOL_ERROR, "PUSH_PROMISE with invalid associated stream (0x0)");
1397
1398 auto it = m_streams.constFind(streamID);
1399#if 0 // Needs to be done after some timeout in case the stream has only just been reset
1400 if (it != m_streams.constEnd()) {
1401 QHttp2Stream *associatedStream = it->get();
1402 if (associatedStream->state() != QHttp2Stream::State::Open
1403 && associatedStream->state() != QHttp2Stream::State::HalfClosedLocal) {
1404 // Cause us to error out below:
1405 it = m_streams.constEnd();
1406 }
1407 }
1408#endif
1409 if (it == m_streams.constEnd())
1410 return connectionError(ENHANCE_YOUR_CALM, "PUSH_PROMISE with invalid associated stream");
1411
1412 const auto reservedID = qFromBigEndian<quint32>(inboundFrame.dataBegin());
1413 if ((reservedID & 1) || reservedID <= m_lastIncomingStreamID || reservedID > lastValidStreamID)
1414 return connectionError(PROTOCOL_ERROR, "PUSH_PROMISE with invalid promised stream ID");
1415
1416 auto *stream = createStreamInternal_impl(reservedID);
1417 if (!stream)
1418 return connectionError(PROTOCOL_ERROR, "PUSH_PROMISE with already active stream ID");
1419 m_lastIncomingStreamID = reservedID;
1421
1422 if (!pushPromiseEnabled) {
1423 // "ignoring a PUSH_PROMISE frame causes the stream state to become
1424 // indeterminate" - let's send RST_STREAM frame with REFUSE_STREAM code.
1425 stream->sendRST_STREAM(REFUSE_STREAM);
1426 }
1427
1428 const bool endHeaders = inboundFrame.flags().testFlag(FrameFlag::END_HEADERS);
1429 continuedFrames.clear();
1430 continuedFrames.push_back(std::move(inboundFrame));
1431
1432 if (!endHeaders) {
1433 continuationExpected = true;
1434 return;
1435 }
1436
1437 handleContinuedHEADERS();
1438}
1439
1440void QHttp2Connection::handlePING()
1441{
1442 Q_ASSERT(inboundFrame.type() == FrameType::PING);
1443 Q_ASSERT(inboundFrame.dataSize() == 8);
1444
1445 if (inboundFrame.streamID() != connectionStreamID)
1446 return connectionError(PROTOCOL_ERROR, "PING on invalid stream");
1447
1448 if (inboundFrame.flags() & FrameFlag::ACK) {
1449 QByteArrayView pingSignature(reinterpret_cast<const char *>(inboundFrame.dataBegin()), 8);
1450 if (!m_lastPingSignature.has_value()) {
1452 qCWarning(qHttp2ConnectionLog, "[%p] PING with ACK received but no PING was sent.", this);
1453 } else if (pingSignature != m_lastPingSignature) {
1455 qCWarning(qHttp2ConnectionLog, "[%p] PING signature does not match the last PING.", this);
1456 } else {
1458 }
1459 m_lastPingSignature.reset();
1460 return;
1461 } else {
1463
1464 }
1465
1466
1467 frameWriter.start(FrameType::PING, FrameFlag::ACK, connectionStreamID);
1468 frameWriter.append(inboundFrame.dataBegin(), inboundFrame.dataBegin() + 8);
1469 frameWriter.write(*getSocket());
1470}
1471
1472void QHttp2Connection::handleGOAWAY()
1473{
1474 // 6.8 GOAWAY
1475
1476 Q_ASSERT(inboundFrame.type() == FrameType::GOAWAY);
1477 // "An endpoint MUST treat a GOAWAY frame with a stream identifier
1478 // other than 0x0 as a connection error (Section 5.4.1) of type PROTOCOL_ERROR."
1479 if (inboundFrame.streamID() != connectionStreamID)
1480 return connectionError(PROTOCOL_ERROR, "GOAWAY on invalid stream");
1481
1482 const uchar *const src = inboundFrame.dataBegin();
1483 quint32 lastStreamID = qFromBigEndian<quint32>(src);
1484 const quint32 errorCode = qFromBigEndian<quint32>(src + 4);
1485
1486 if (!lastStreamID) {
1487 // "The last stream identifier can be set to 0 if no
1488 // streams were processed."
1489 lastStreamID = 1;
1490 } else if (!(lastStreamID & 0x1)) {
1491 // 5.1.1 - we (client) use only odd numbers as stream identifiers.
1492 return connectionError(PROTOCOL_ERROR, "GOAWAY with invalid last stream ID");
1493 } else if (lastStreamID >= m_nextStreamID) {
1494 // "A server that is attempting to gracefully shut down a connection SHOULD
1495 // send an initial GOAWAY frame with the last stream identifier set to 2^31-1
1496 // and a NO_ERROR code."
1497 if (lastStreamID != lastValidStreamID || errorCode != HTTP2_NO_ERROR)
1498 return connectionError(PROTOCOL_ERROR, "GOAWAY invalid stream/error code");
1499 } else {
1500 lastStreamID += 2;
1501 }
1502
1503 m_goingAway = true;
1504
1505 emit receivedGOAWAY(errorCode, lastStreamID);
1506
1507 for (quint32 id = lastStreamID; id < m_nextStreamID; id += 2) {
1508 QHttp2Stream *stream = m_streams.value(id, nullptr);
1509 if (stream && stream->isActive())
1510 stream->finishWithError(errorCode, "Received GOAWAY"_L1);
1511 }
1512
1513 const auto isActive = [](const QHttp2Stream *stream) { return stream && stream->isActive(); };
1514 if (std::none_of(m_streams.cbegin(), m_streams.cend(), isActive))
1515 closeSession();
1516}
1517
1518void QHttp2Connection::handleWINDOW_UPDATE()
1519{
1520 Q_ASSERT(inboundFrame.type() == FrameType::WINDOW_UPDATE);
1521
1522 const quint32 delta = qFromBigEndian<quint32>(inboundFrame.dataBegin());
1523 const bool valid = delta && delta <= quint32(std::numeric_limits<qint32>::max());
1524 const auto streamID = inboundFrame.streamID();
1525
1526 qCDebug(qHttp2ConnectionLog(), "[%p] Received WINDOW_UPDATE, stream %d, delta %d", this,
1527 streamID, delta);
1528 if (streamID == connectionStreamID) {
1529 qint32 sum = 0;
1530 if (!valid || qAddOverflow(sessionSendWindowSize, qint32(delta), &sum))
1531 return connectionError(PROTOCOL_ERROR, "WINDOW_UPDATE invalid delta");
1532 sessionSendWindowSize = sum;
1533 for (auto &stream : m_streams) {
1534 if (!stream || !stream->isActive())
1535 continue;
1536 // Stream may have been unblocked, so maybe try to write again
1537 if (stream->isUploadingDATA() && !stream->isUploadBlocked())
1538 QMetaObject::invokeMethod(stream, &QHttp2Stream::maybeResumeUpload,
1540 }
1541 } else {
1542 QHttp2Stream *stream = m_streams.value(streamID);
1543 if (!stream || !stream->isActive()) {
1544 // WINDOW_UPDATE on closed streams can be ignored.
1545 qCDebug(qHttp2ConnectionLog, "[%p] Received WINDOW_UPDATE on closed stream %d", this,
1546 streamID);
1547 return;
1548 }
1549 stream->handleWINDOW_UPDATE(inboundFrame);
1550 }
1551}
1552
1553void QHttp2Connection::handleCONTINUATION()
1554{
1555 Q_ASSERT(inboundFrame.type() == FrameType::CONTINUATION);
1556 if (continuedFrames.empty())
1557 return connectionError(PROTOCOL_ERROR,
1558 "CONTINUATION without a preceding HEADERS or PUSH_PROMISE");
1559
1560 if (inboundFrame.streamID() != continuedFrames.front().streamID())
1561 return connectionError(PROTOCOL_ERROR, "CONTINUATION on invalid stream");
1562
1563 const bool endHeaders = inboundFrame.flags().testFlag(FrameFlag::END_HEADERS);
1564 continuedFrames.push_back(std::move(inboundFrame));
1565
1566 if (!endHeaders)
1567 return;
1568
1569 continuationExpected = false;
1570 handleContinuedHEADERS();
1571}
1572
1573void QHttp2Connection::handleContinuedHEADERS()
1574{
1575 // 'Continued' HEADERS can be: the initial HEADERS/PUSH_PROMISE frame
1576 // with/without END_HEADERS flag set plus, if no END_HEADERS flag,
1577 // a sequence of one or more CONTINUATION frames.
1578 Q_ASSERT(!continuedFrames.empty());
1579 const auto firstFrameType = continuedFrames[0].type();
1580 Q_ASSERT(firstFrameType == FrameType::HEADERS || firstFrameType == FrameType::PUSH_PROMISE);
1581
1582 const auto streamID = continuedFrames[0].streamID();
1583
1584 const auto streamIt = m_streams.constFind(streamID);
1585 if (firstFrameType == FrameType::HEADERS) {
1586 if (streamIt != m_streams.cend()) {
1587 QHttp2Stream *stream = streamIt.value();
1590 && stream->state() != QHttp2Stream::State::Idle
1591 && stream->state() != QHttp2Stream::State::Open) {
1592 // We can receive HEADERS on streams initiated by our requests
1593 // (these streams are in halfClosedLocal or open state) or
1594 // remote-reserved streams from a server's PUSH_PROMISE.
1595 stream->finishWithError(QNetworkReply::ProtocolFailure,
1596 "HEADERS on invalid stream"_L1);
1597 stream->sendRST_STREAM(CANCEL);
1598 return;
1599 }
1600 }
1601 // Else: we cannot just ignore our peer's HEADERS frames - they change
1602 // HPACK context - even though the stream was reset; apparently the peer
1603 // has yet to see the reset.
1604 }
1605
1606 std::vector<uchar> hpackBlock(assemble_hpack_block(continuedFrames));
1607 const bool hasHeaderFields = !hpackBlock.empty();
1608 if (hasHeaderFields) {
1609 HPack::BitIStream inputStream{ hpackBlock.data(), hpackBlock.data() + hpackBlock.size() };
1610 if (!decoder.decodeHeaderFields(inputStream))
1611 return connectionError(COMPRESSION_ERROR, "HPACK decompression failed");
1612 } else {
1613 if (firstFrameType == FrameType::PUSH_PROMISE) {
1614 // It could be a PRIORITY sent in HEADERS - already handled by this
1615 // point in handleHEADERS. If it was PUSH_PROMISE (HTTP/2 8.2.1):
1616 // "The header fields in PUSH_PROMISE and any subsequent CONTINUATION
1617 // frames MUST be a valid and complete set of request header fields
1618 // (Section 8.1.2.3) ... If a client receives a PUSH_PROMISE that does
1619 // not include a complete and valid set of header fields or the :method
1620 // pseudo-header field identifies a method that is not safe, it MUST
1621 // respond with a stream error (Section 5.4.2) of type PROTOCOL_ERROR."
1622 if (streamIt != m_streams.cend())
1623 (*streamIt)->sendRST_STREAM(PROTOCOL_ERROR);
1624 return;
1625 }
1626
1627 // We got back an empty hpack block. Now let's figure out if there was an error.
1628 constexpr auto hpackBlockHasContent = [](const auto &c) { return c.hpackBlockSize() > 0; };
1629 const bool anyHpackBlock = std::any_of(continuedFrames.cbegin(), continuedFrames.cend(),
1630 hpackBlockHasContent);
1631 if (anyHpackBlock) // There was hpack block data, but returned empty => it overflowed.
1632 return connectionError(FRAME_SIZE_ERROR, "HEADERS frame too large");
1633 }
1634
1635 if (streamIt == m_streams.cend()) // No more processing without a stream from here on.
1636 return;
1637
1638 switch (firstFrameType) {
1639 case FrameType::HEADERS:
1640 streamIt.value()->handleHEADERS(continuedFrames[0].flags(), decoder.decodedHeader());
1641 break;
1642 case FrameType::PUSH_PROMISE: {
1643 std::optional<QUrl> promiseKey = HPack::makePromiseKeyUrl(decoder.decodedHeader());
1644 if (!promiseKey)
1645 return; // invalid URL/key !
1646 if (m_promisedStreams.contains(*promiseKey))
1647 return; // already promised!
1648 const auto promiseID = qFromBigEndian<quint32>(continuedFrames[0].dataBegin());
1649 QHttp2Stream *stream = m_streams.value(promiseID);
1650 stream->transitionState(QHttp2Stream::StateTransition::CloseLocal);
1651 stream->handleHEADERS(continuedFrames[0].flags(), decoder.decodedHeader());
1652 emit newPromisedStream(stream); // @future[consider] add promise key as argument?
1653 m_promisedStreams.emplace(*promiseKey, promiseID);
1654 break;
1655 }
1656 default:
1657 break;
1658 }
1659}
1660
1661bool QHttp2Connection::acceptSetting(Http2::Settings identifier, quint32 newValue)
1662{
1663 switch (identifier) {
1664 case Settings::HEADER_TABLE_SIZE_ID: {
1665 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS HEADER_TABLE_SIZE %d", this, newValue);
1666 if (newValue > maxAcceptableTableSize) {
1667 connectionError(PROTOCOL_ERROR, "SETTINGS invalid table size");
1668 return false;
1669 }
1670 encoder.setMaxDynamicTableSize(newValue);
1671 break;
1672 }
1673 case Settings::INITIAL_WINDOW_SIZE_ID: {
1674 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS INITIAL_WINDOW_SIZE %d", this,
1675 newValue);
1676 // For every active stream - adjust its window
1677 // (and handle possible overflows as errors).
1678 if (newValue > quint32(std::numeric_limits<qint32>::max())) {
1679 connectionError(FLOW_CONTROL_ERROR, "SETTINGS invalid initial window size");
1680 return false;
1681 }
1682
1683 const qint32 delta = qint32(newValue) - streamInitialSendWindowSize;
1684 streamInitialSendWindowSize = qint32(newValue);
1685
1686 qCDebug(qHttp2ConnectionLog, "[%p] Adjusting initial window size for %zu streams by %d",
1687 this, size_t(m_streams.size()), delta);
1688 for (const QPointer<QHttp2Stream> &stream : std::as_const(m_streams)) {
1689 if (!stream || !stream->isActive())
1690 continue;
1691 qint32 sum = 0;
1692 if (qAddOverflow(stream->m_sendWindow, delta, &sum)) {
1693 stream->sendRST_STREAM(PROTOCOL_ERROR);
1694 stream->finishWithError(QNetworkReply::ProtocolFailure,
1695 "SETTINGS window overflow"_L1);
1696 continue;
1697 }
1698 stream->m_sendWindow = sum;
1699 if (delta > 0 && stream->isUploadingDATA() && !stream->isUploadBlocked()) {
1700 QMetaObject::invokeMethod(stream, &QHttp2Stream::maybeResumeUpload,
1702 }
1703 }
1704 break;
1705 }
1706 case Settings::MAX_CONCURRENT_STREAMS_ID: {
1707 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS MAX_CONCURRENT_STREAMS %d", this,
1708 newValue);
1709 m_maxConcurrentStreams = newValue;
1710 break;
1711 }
1712 case Settings::MAX_FRAME_SIZE_ID: {
1713 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS MAX_FRAME_SIZE %d", this, newValue);
1714 if (newValue < Http2::minPayloadLimit || newValue > Http2::maxPayloadSize) {
1715 connectionError(PROTOCOL_ERROR, "SETTINGS max frame size is out of range");
1716 return false;
1717 }
1718 maxFrameSize = newValue;
1719 break;
1720 }
1721 case Settings::MAX_HEADER_LIST_SIZE_ID: {
1722 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS MAX_HEADER_LIST_SIZE %d", this,
1723 newValue);
1724 // We just remember this value, it can later
1725 // prevent us from sending any request (and this
1726 // will end up in request/reply error).
1727 m_maxHeaderListSize = newValue;
1728 break;
1729 }
1731 qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS ENABLE_PUSH %d", this, newValue);
1732 if (newValue != 0 && newValue != 1) {
1733 connectionError(PROTOCOL_ERROR, "SETTINGS peer sent illegal value for ENABLE_PUSH");
1734 return false;
1735 }
1736 if (m_connectionType == Type::Client) {
1737 if (newValue == 1) {
1738 connectionError(PROTOCOL_ERROR, "SETTINGS server sent ENABLE_PUSH=1");
1739 return false;
1740 }
1741 } else { // server-side
1742 pushPromiseEnabled = newValue;
1743 break;
1744 }
1745 }
1746
1747 return true;
1748}
1749
1751
1752#include "moc_qhttp2connection_p.cpp"
IOBluetoothDevice * device
bool isActive
const HttpHeader & decodedHeader() const
Definition hpack_p.h:91
bool decodeHeaderFields(class BitIStream &inputStream)
Definition hpack.cpp:378
void setCompressStrings(bool compress)
Definition hpack.cpp:175
void setMaxDynamicTableSize(quint32 size)
Definition hpack.cpp:168
FrameStatus read(QIODevice &socket)
bool writeHEADERS(QIODevice &socket, quint32 sizeLimit)
void addFlag(FrameFlag flag)
void append(ValueType val)
void setOutboundFrame(Frame &&newFrame)
Frame & outboundFrame()
void start(FrameType type, FrameFlags flags, quint32 streamID)
bool write(QIODevice &socket) const
qint64 bytesAvailable() const override
Returns the number of incoming bytes that are waiting to be read.
void close() override
Closes the I/O device for the socket and calls disconnectFromHost() to close the socket's connection.
\inmodule QtCore
Definition qbytearray.h:57
void append(const QByteDataBuffer &other)
Definition qbytedata_p.h:48
static QString translate(const char *context, const char *key, const char *disambiguation=nullptr, int n=-1)
\threadsafe
iterator begin()
Returns an \l{STL-style iterators}{STL-style iterator} pointing to the first item in the hash.
Definition qhash.h:1212
const_iterator cbegin() const noexcept
Definition qhash.h:1214
qsizetype size() const noexcept
Returns the number of items in the hash.
Definition qhash.h:927
const_iterator constFind(const Key &key) const noexcept
Definition qhash.h:1299
const_iterator constEnd() const noexcept
Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the ...
Definition qhash.h:1219
iterator emplace(const Key &key, Args &&... args)
Definition qhash.h:1324
bool contains(const Key &key) const noexcept
Returns true if the hash contains an item with the key; otherwise returns false.
Definition qhash.h:1007
T value(const Key &key) const noexcept
Definition qhash.h:1054
iterator end() noexcept
Returns an \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the last ...
Definition qhash.h:1216
const_iterator cend() const noexcept
Definition qhash.h:1218
The QHttp2Configuration class controls HTTP/2 parameters and settings.
unsigned sessionReceiveWindowSize() const
Returns the window size for connection-level flow control.
unsigned streamReceiveWindowSize() const
Returns the window size for stream-level flow control.
bool serverPushEnabled() const
Returns true if server push was enabled.
bool huffmanCompressionEnabled() const
Returns true if the Huffman coding in HPACK is enabled.
\inmodule QtNetwork
QHttp2Stream * getStream(quint32 streamId) const
Return a pointer to a stream with the given streamID, or null if no such stream exists or it was dele...
void receivedGOAWAY(quint32 errorCode, quint32 lastStreamID)
This signal is emitted when the connection has received a GOAWAY frame.
void pingFrameRecived(QHttp2Connection::PingState state)
void errorOccurred(Http2::Http2Error errorCode, const QString &errorString)
This signal is emitted when the connection has encountered an error.
QH2Expected< QHttp2Stream *, CreateStreamError > createStream()
Creates a stream on this connection.
void connectionClosed()
This signal is emitted when the connection has been closed.
friend class QHttp2Stream
void settingsFrameReceived()
This signal is emitted when the connection has received a SETTINGS frame.
void close()
This sends a GOAWAY frame on the connection stream, gracefully closing the connection.
static QHttp2Connection * createUpgradedConnection(QIODevice *socket, const QHttp2Configuration &config)
Create a new HTTP2 connection given a config and a socket.
static QHttp2Connection * createDirectServerConnection(QIODevice *socket, const QHttp2Configuration &config)
Create a new HTTP2 connection given a config and a socket.
void handleConnectionClosure()
This function must be called when the socket has been disconnected, and will end all remaining stream...
static QHttp2Connection * createDirectConnection(QIODevice *socket, const QHttp2Configuration &config)
Create a new HTTP2 connection given a config and a socket.
void newIncomingStream(QHttp2Stream *stream)
This signal is emitted when a new stream is received from the remote peer.
void newPromisedStream(QHttp2Stream *stream)
This signal is emitted when the remote peer has promised a new stream.
quint32 maxHeaderListSize() const noexcept
Returns the maximum size of the header which the peer is willing to accept.
void handleReadyRead()
This function must be called when you have received a readyRead signal (or equivalent) from the QIODe...
\inmodule QtNetwork
void dataReceived(const QByteArray &data, bool endStream)
This signal is emitted when the stream has received a DATA frame from the remote peer.
void stateChanged(QHttp2Stream::State newState)
This signal is emitted when the state of the stream changes.
void sendDATA(QIODevice *device, bool endStream)
Sends a DATA frame with the bytes obtained from device.
~QHttp2Stream() noexcept
quint32 streamID() const noexcept
Returns the stream ID of this stream.
void uploadDeviceError(const QString &errorString)
This signal is emitted if the upload device encounters an error while sending data.
void uploadBlocked()
This signal is emitted when the stream is unable to send more data because the remote peer's receive ...
bool isUploadingDATA() const noexcept
Returns true if the stream is currently sending DATA frames.
void sendWINDOW_UPDATE(quint32 delta)
Sends a WINDOW_UPDATE frame with the given delta.
bool sendHEADERS(const HPack::HttpHeader &headers, bool endStream, quint8 priority=DefaultPriority)
Sends a HEADERS frame with the given headers and priority.
bool isUploadBlocked() const noexcept
Returns true if the stream is currently unable to send more data because the remote peer's receive wi...
bool sendRST_STREAM(quint32 errorCode)
Sends a RST_STREAM frame with the given errorCode.
void uploadFinished()
This signal is emitted when the stream has finished sending all the data from the upload device.
void headersUpdated()
This signal may be emitted if a new HEADERS frame was received after already processing a previous HE...
void errorOccurred(quint32 errorCode, const QString &errorString)
This signal is emitted when the stream has encountered an error.
void headersReceived(const HPack::HttpHeader &headers, bool endStream)
This signal is emitted when the remote peer has sent a HEADERS frame, and potentially some CONTINUATI...
\inmodule QtCore \reentrant
Definition qiodevice.h:34
bool isOpen() const
Returns true if the device is open; otherwise returns false.
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
QIODeviceBase::OpenMode openMode() const
Returns the mode in which the device has been opened; i.e.
qint64 read(char *data, qint64 maxlen)
Reads at most maxSize bytes from the device into data, and returns the number of bytes read.
NetworkError
Indicates all possible error conditions found during the processing of the request.
static QNonContiguousByteDevice * create(QIODevice *device)
Create a QNonContiguousByteDevice out of a QIODevice.
virtual const char * readPointer(qint64 maximumLength, qint64 &len)=0
Return a byte pointer for at most maximumLength bytes of that device.
virtual qint64 size() const =0
Returns the size of the complete device or -1 if unknown.
virtual bool advanceReadPointer(qint64 amount)=0
will advance the internal read pointer by amount bytes.
void readyRead()
Emitted when there is data available.
virtual bool atEnd() const =0
Returns true if everything has been read and the read pointer cannot be advanced anymore.
\inmodule QtCore
Definition qobject.h:103
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
void destroyed(QObject *=nullptr)
This signal is emitted immediately before the object obj is destroyed, after any instances of QPointe...
void deleteLater()
\threadsafe
Definition qobject.cpp:2435
\inmodule QtCore \reentrant
Definition qrandom.h:21
quint32 generate()
Generates a 32-bit random quantity and returns it.
Definition qrandom.h:48
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
QSet< QString >::iterator it
void newState(QList< State > &states, const char *token, const char *lexem, bool pre)
std::vector< HeaderField > HttpHeader
Definition hpack_p.h:33
std::optional< QUrl > makePromiseKeyUrl(const HttpHeader &requestHeader)
Definition hpack.cpp:507
std::vector< uchar > assemble_hpack_block(const std::vector< Frame > &frames)
const char Http2clientPreface[clientPrefaceLength]
const quint32 lastValidStreamID((quint32(1)<< 31) - 1)
@ frameHeaderSize
@ defaultSessionWindowSize
@ connectionStreamID
@ clientPrefaceLength
Frame configurationToSettingsFrame(const QHttp2Configuration &config)
@ COMPRESSION_ERROR
@ ENHANCE_YOUR_CALM
@ FLOW_CONTROL_ERROR
void qt_error(quint32 errorCode, QNetworkReply::NetworkError &error, QString &errorMessage)
Combined button and popup list for selecting options.
@ QueuedConnection
DBusConnection const char DBusError * error
DBusConnection * connection
EGLStreamKHR stream
EGLConfig config
@ QtCriticalMsg
Definition qlogging.h:32
#define Q_LOGGING_CATEGORY(name,...)
#define qCCritical(category,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
std::enable_if_t< std::is_unsigned_v< T >, bool > qAddOverflow(T v1, T v2, T *r)
Definition qnumeric.h:113
GLenum GLsizei GLuint GLint * bytesWritten
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint GLuint end
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint GLuint GLfloat weight
GLenum src
GLenum GLuint buffer
GLbitfield flags
GLuint GLsizei const GLchar * message
GLint GLint GLint GLint GLint GLint GLint GLbitfield mask
const GLubyte * c
GLuint64EXT * result
[6]
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define qUtf16Printable(string)
Definition qstring.h:1543
#define emit
#define Q_UNUSED(x)
unsigned int quint32
Definition qtypes.h:50
unsigned char uchar
Definition qtypes.h:32
int qint32
Definition qtypes.h:49
ptrdiff_t qsizetype
Definition qtypes.h:165
unsigned int uint
Definition qtypes.h:34
long long qint64
Definition qtypes.h:60
unsigned char quint8
Definition qtypes.h:46
ReturnedValue read(const char *data)
myObject disconnect()
[26]
QTcpSocket * socket
[1]
bool priority(quint32 *streamID=nullptr, uchar *weight=nullptr) const
const uchar * dataBegin() const
quint32 payloadSize() const
quint32 streamID() const
quint32 dataSize() const
std::vector< uchar > buffer
FrameType type() const
FrameFlags flags() const
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...