6#include <private/bitstreams_p.h>
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>
201 qCDebug(qHttp2ConnectionLog,
"[%p] stream %u finished with error: %ls (error code: %u)",
203 transitionState(StateTransition::RST);
207void QHttp2Stream::finishWithError(
quint32 errorCode)
226 qCDebug(qHttp2ConnectionLog,
"[%p] sending RST_STREAM on stream %u, code: %u", getConnection(),
227 m_streamID, errorCode);
228 transitionState(StateTransition::RST);
232 frameWriter.
start(FrameType::RST_STREAM, FrameFlag::EMPTY, m_streamID);
233 frameWriter.
append(errorCode);
257 byteDevice->setParent(
this);
259 qCDebug(qHttp2ConnectionLog,
"[%p] starting sendDATA on stream %u, of IODevice: %p",
260 getConnection(), m_streamID,
device);
281 qCDebug(qHttp2ConnectionLog,
"[%p] starting sendDATA on stream %u, of device: %p",
282 getConnection(), m_streamID,
device);
284 m_uploadByteDevice =
device;
285 m_endStreamAfterDATA = endStream;
287 &QHttp2Stream::maybeResumeUpload);
293void QHttp2Stream::internalSendDATA()
301 "[%p] stream %u, about to write to socket, current session window size: %d, stream "
302 "window size: %d, bytes available: %lld",
304 m_uploadByteDevice->
size() - m_uploadByteDevice->
pos());
306 qint32 remainingWindowSize = std::min<qint32>(
connection->sessionSendWindowSize, m_sendWindow);
308 qint64 totalBytesWritten = 0;
309 const auto deviceCanRead = [
this,
connection] {
316 return m_uploadByteDevice->
readPointer(requestSize, tmp) !=
nullptr && tmp > 0;
319 bool sentEND_STREAM =
false;
320 while (remainingWindowSize && deviceCanRead()) {
323 frameWriter.
start(FrameType::DATA, FrameFlag::EMPTY,
streamID());
325 while (remainingWindowSize && deviceCanRead() && remainingBytesInFrame) {
326 const qint32 maxToWrite = std::min(remainingWindowSize, remainingBytesInFrame);
329 const char *readPointer = m_uploadByteDevice->
readPointer(maxToWrite, outBytesAvail);
330 if (!readPointer || outBytesAvail <= 0) {
332 "[%p] stream %u, cannot write data, device (%p) has %lld bytes available",
333 connection, m_streamID, m_uploadByteDevice, outBytesAvail);
336 const qint32 bytesToWrite =
qint32(std::min<qint64>(maxToWrite, outBytesAvail));
342 m_sendWindow -= bytesToWrite;
344 connection->sessionSendWindowSize -= bytesToWrite;
346 remainingBytesInFrame -= bytesToWrite;
347 Q_ASSERT(remainingBytesInFrame >= 0);
348 remainingWindowSize -= bytesToWrite;
352 qCDebug(qHttp2ConnectionLog,
"[%p] stream %u, writing %u bytes to socket",
connection,
354 if (!deviceCanRead() && m_uploadByteDevice->
atEnd() && m_endStreamAfterDATA) {
355 sentEND_STREAM =
true;
356 frameWriter.
addFlag(FrameFlag::END_STREAM);
359 qCDebug(qHttp2ConnectionLog,
"[%p] stream %u, failed to write to socket",
connection,
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);
374 if (sentEND_STREAM || (!deviceCanRead() && m_uploadByteDevice->
atEnd())) {
376 "[%p] stream %u, exhausted device %p, sent END_STREAM? %d, %ssending end stream "
378 connection, m_streamID, m_uploadByteDevice, sentEND_STREAM,
379 m_endStreamAfterDATA ?
"" :
"not ");
380 if (!sentEND_STREAM && m_endStreamAfterDATA) {
385 frameWriter.
start(FrameType::DATA, FrameFlag::END_STREAM,
streamID());
390 qCDebug(qHttp2ConnectionLog,
"[%p] stream %u, upload blocked",
connection, m_streamID);
395void QHttp2Stream::finishSendDATA()
397 if (m_endStreamAfterDATA)
398 transitionState(StateTransition::CloseLocal);
400 disconnect(m_uploadByteDevice,
nullptr,
this,
nullptr);
401 m_uploadDevice =
nullptr;
402 m_uploadByteDevice =
nullptr;
406void QHttp2Stream::maybeResumeUpload()
409 "[%p] stream %u, maybeResumeUpload. Upload device: %p, bytes available: %lld, blocked? "
411 getConnection(), m_streamID, m_uploadByteDevice,
412 !m_uploadByteDevice ? 0 : m_uploadByteDevice->
size() - m_uploadByteDevice->
pos(),
426 && (m_sendWindow <= MinFrameSize
427 || getConnection()->sessionSendWindowSize <= MinFrameSize);
430void QHttp2Stream::uploadDeviceReadChannelFinished()
444 using namespace HPack;
445 if (
auto hs = header_size(headers);
450 transitionState(StateTransition::Open);
456 qCDebug(qHttp2ConnectionLog,
"[%p] stream %u, sending HEADERS frame with %u entries",
462 frameWriter.
start(FrameType::HEADERS, FrameFlag::PRIORITY | FrameFlag::END_HEADERS,
streamID());
464 frameWriter.
addFlag(FrameFlag::END_STREAM);
467 frameWriter.
append(priority);
471 if (
connection->m_connectionType == QHttp2Connection::Type::Client) {
472 if (!
connection->encoder.encodeRequest(outputStream, headers))
475 if (!
connection->encoder.encodeResponse(outputStream, headers))
481 transitionState(StateTransition::CloseLocal);
494 m_recvWindow +=
qint32(delta);
498void QHttp2Stream::uploadDeviceDestroyed()
506 m_uploadDevice =
nullptr;
513 qCDebug(qHttp2ConnectionLog,
"[%p] stream %u, state changed from %d to %d", getConnection(),
522void QHttp2Stream::transitionState(StateTransition transition)
526 if (transition == StateTransition::Open)
532 switch (transition) {
533 case StateTransition::CloseLocal:
536 case StateTransition::CloseRemote:
539 case StateTransition::RST:
542 case StateTransition::Open:
547 if (transition == StateTransition::CloseRemote || transition == StateTransition::RST)
551 if (transition == StateTransition::CloseLocal || transition == StateTransition::RST)
555 if (transition == StateTransition::RST) {
557 }
else if (transition == StateTransition::CloseLocal) {
566void QHttp2Stream::handleDATA(
const Frame &inboundFrame)
570 qCDebug(qHttp2ConnectionLog,
"[%p] stream %u, received DATA frame with payload of %u bytes",
575 "[%p] stream %u, received DATA frame with payload size %u, "
576 "but recvWindow is %d, sending FLOW_CONTROL_ERROR",
583 const bool endStream = inboundFrame.
flags().testFlag(FrameFlag::END_STREAM);
585 if (inboundFrame.
dataSize() > 0 || endStream) {
589 transitionState(StateTransition::CloseRemote);
591 m_downloadBuffer.
append(std::move(fragment));
594 if (!endStream && m_recvWindow < connection->streamInitialReceiveWindowSize / 2) {
600void QHttp2Stream::handleHEADERS(Http2::FrameFlags frameFlags,
const HPack::HttpHeader &headers)
603 transitionState(StateTransition::Open);
604 const bool endStream = frameFlags.testFlag(FrameFlag::END_STREAM);
606 transitionState(StateTransition::CloseRemote);
607 if (!headers.empty()) {
608 m_headers.insert(m_headers.end(), headers.begin(), headers.end());
614void QHttp2Stream::handleRST_STREAM(
const Frame &inboundFrame)
616 transitionState(StateTransition::RST);
617 m_RST_STREAM_code = qFromBigEndian<quint32>(inboundFrame.
dataBegin());
619 disconnect(m_uploadByteDevice,
nullptr,
this,
nullptr);
620 m_uploadDevice =
nullptr;
621 m_uploadByteDevice =
nullptr;
623 finishWithError(*m_RST_STREAM_code,
""_L1);
626void QHttp2Stream::handleWINDOW_UPDATE(
const Frame &inboundFrame)
629 const bool valid = delta && delta <=
quint32(std::numeric_limits<qint32>::max());
633 "[%p] stream %u, received WINDOW_UPDATE frame with invalid delta %u, sending "
635 getConnection(), m_streamID, delta);
727 connection->m_connectionType = QHttp2Connection::Type::Client;
754 connection->m_connectionType = QHttp2Connection::Type::Client;
774 connection->m_connectionType = QHttp2Connection::Type::Server;
793 Q_ASSERT(m_connectionType == Type::Client);
796 return createStreamInternal();
799QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError>
800QHttp2Connection::createStreamInternal()
804 const quint32 streamID = m_nextStreamID;
805 if (
size_t(m_maxConcurrentStreams) <=
size_t(numActiveLocalStreams()))
808 return { createStreamInternal_impl(streamID) };
814 QPointer<QHttp2Stream> &
stream = m_streams[streamID];
815 if (numStreams == m_streams.
size())
818 stream->m_recvWindow = streamInitialReceiveWindowSize;
819 stream->m_sendWindow = streamInitialSendWindowSize;
825 const auto shouldCount = [
mask](
const QPointer<QHttp2Stream> &
stream) ->
bool {
828 return std::count_if(m_streams.cbegin(), m_streams.cend(), shouldCount);
835qsizetype QHttp2Connection::numActiveRemoteStreams() const noexcept
837 const quint32 RemoteMask = m_connectionType == Type::Client ? 0 : 1;
838 return numActiveStreamsImpl(RemoteMask);
845qsizetype QHttp2Connection::numActiveLocalStreams() const noexcept
847 const quint32 LocalMask = m_connectionType == Type::Client ? 1 : 0;
848 return numActiveStreamsImpl(LocalMask);
857 return m_streams.
value(streamID,
nullptr).get();
918 for (QPointer<QHttp2Stream> &
stream : std::exchange(m_streams, {}))
922bool QHttp2Connection::serverCheckClientPreface()
924 if (!m_waitingForClientPreface)
926 auto *
socket = getSocket();
929 if (!readClientPreface()) {
932 qCDebug(qHttp2ConnectionLog,
"[%p] Invalid client preface",
this);
935 qCDebug(qHttp2ConnectionLog,
"[%p] Peer sent valid client preface",
this);
936 m_waitingForClientPreface =
false;
937 if (!sendServerPreface()) {
946 std::array<char, 8>
data;
958 if (!m_lastPingSignature) {
959 m_lastPingSignature =
data.toByteArray();
961 qCWarning(qHttp2ConnectionLog,
"[%p] No PING is sent while waiting for the previous PING.",
this);
966 frameWriter.
write(*getSocket());
978 if (m_connectionType == Type::Server && !serverCheckClientPreface())
981 const auto streamIsActive = [](
const QPointer<QHttp2Stream> &
stream) {
984 if (m_goingAway && std::none_of(m_streams.
cbegin(), m_streams.
cend(), streamIsActive)) {
990 qCDebug(qHttp2ConnectionLog,
"[%p] Receiving data, %lld bytes available",
this,
993 using namespace Http2;
994 while (!m_goingAway || std::any_of(m_streams.
cbegin(), m_streams.
cend(), streamIsActive)) {
996 if (
result != FrameStatus::goodFrame)
997 qCDebug(qHttp2ConnectionLog,
"[%p] Tried to read frame, got %d",
this,
int(
result));
999 case FrameStatus::incompleteFrame:
1001 case FrameStatus::protocolError:
1003 case FrameStatus::sizeError:
1013 const auto frameType = inboundFrame.
type();
1014 qCDebug(qHttp2ConnectionLog,
"[%p] Successfully read a frame, with type: %d",
this,
1016 if (continuationExpected && frameType != FrameType::CONTINUATION)
1019 switch (frameType) {
1020 case FrameType::DATA:
1023 case FrameType::HEADERS:
1026 case FrameType::PRIORITY:
1029 case FrameType::RST_STREAM:
1032 case FrameType::SETTINGS:
1035 case FrameType::PUSH_PROMISE:
1036 handlePUSH_PROMISE();
1038 case FrameType::PING:
1041 case FrameType::GOAWAY:
1044 case FrameType::WINDOW_UPDATE:
1045 handleWINDOW_UPDATE();
1047 case FrameType::CONTINUATION:
1048 handleCONTINUATION();
1050 case FrameType::LAST_FRAME_TYPE:
1057bool QHttp2Connection::readClientPreface()
1059 auto *
socket = getSocket();
1084 m_config = std::move(
config);
1093void QHttp2Connection::connectionError(
Http2Error errorCode,
const char *
message)
1099 qCCritical(qHttp2ConnectionLog,
"[%p] Connection error: %s (%d)",
this,
message,
1103 sendGOAWAY(errorCode);
1115void QHttp2Connection::closeSession()
1120bool QHttp2Connection::streamWasReset(
quint32 streamID)
noexcept
1122 return m_resetStreamIDs.contains(streamID);
1125bool QHttp2Connection::isInvalidStream(
quint32 streamID)
noexcept
1127 auto stream = m_streams.value(streamID,
nullptr);
1128 return !
stream && !streamWasReset(streamID);
1131bool QHttp2Connection::sendClientPreface()
1139 if (!sendSETTINGS()) {
1140 qCWarning(qHttp2ConnectionLog,
"[%p] Failed to send SETTINGS",
this);
1146bool QHttp2Connection::sendServerPreface()
1150 if (!sendSETTINGS()) {
1151 qCWarning(qHttp2ConnectionLog,
"[%p] Failed to send SETTINGS",
this);
1157bool QHttp2Connection::sendSETTINGS()
1162 qCDebug(qHttp2ConnectionLog,
"[%p] Sending SETTINGS frame, %d bytes",
this,
1169 sessionReceiveWindowSize = maxSessionReceiveWindowSize;
1176 waitingForSettingsACK =
true;
1180bool QHttp2Connection::sendWINDOW_UPDATE(
quint32 streamID,
quint32 delta)
1182 qCDebug(qHttp2ConnectionLog,
"[%p] Sending WINDOW_UPDATE frame, stream %d, delta %u",
this,
1184 frameWriter.
start(FrameType::WINDOW_UPDATE, FrameFlag::EMPTY, streamID);
1185 frameWriter.
append(delta);
1186 return frameWriter.
write(*getSocket());
1189bool QHttp2Connection::sendGOAWAY(
quint32 errorCode)
1191 frameWriter.
start(FrameType::GOAWAY, FrameFlag::EMPTY,
1192 Http2PredefinedParameters::connectionStreamID);
1194 frameWriter.
append(errorCode);
1195 return frameWriter.
write(*getSocket());
1198bool QHttp2Connection::sendSETTINGS_ACK()
1201 return frameWriter.
write(*getSocket());
1204void QHttp2Connection::handleDATA()
1208 const auto streamID = inboundFrame.
streamID();
1210 return connectionError(
PROTOCOL_ERROR,
"DATA on the connection stream");
1212 if (isInvalidStream(streamID))
1217 "[%p] Received DATA frame with payload size %u, "
1218 "but recvWindow is %d, sending FLOW_CONTROL_ERROR",
1219 this, inboundFrame.
payloadSize(), sessionReceiveWindowSize);
1223 sessionReceiveWindowSize -= inboundFrame.
payloadSize();
1226 if (
it != m_streams.
cend() &&
it.value())
1227 it.value()->handleDATA(inboundFrame);
1229 if (sessionReceiveWindowSize < maxSessionReceiveWindowSize / 2) {
1233 quint32(maxSessionReceiveWindowSize - sessionReceiveWindowSize));
1234 sessionReceiveWindowSize = maxSessionReceiveWindowSize;
1238void QHttp2Connection::handleHEADERS()
1242 const auto streamID = inboundFrame.
streamID();
1243 qCDebug(qHttp2ConnectionLog,
"[%p] Received HEADERS frame on stream %d",
this, streamID);
1248 const bool isClient = m_connectionType == Type::Client;
1249 const bool isClientInitiatedStream = !!(streamID & 1);
1250 const bool isRemotelyInitiatedStream = isClient ^ isClientInitiatedStream;
1252 if (isRemotelyInitiatedStream && streamID > m_lastIncomingStreamID) {
1253 QHttp2Stream *newStream = createStreamInternal_impl(streamID);
1255 m_lastIncomingStreamID = streamID;
1256 qCDebug(qHttp2ConnectionLog,
"[%p] Created new incoming stream %d",
this, streamID);
1259 qCDebug(qHttp2ConnectionLog,
"[%p] Received HEADERS on non-existent stream %d",
this,
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);
1268 if (
flags.testFlag(FrameFlag::PRIORITY)) {
1269 qCDebug(qHttp2ConnectionLog,
"[%p] HEADERS frame on stream %d has PRIORITY flag",
this,
1276 const bool endHeaders =
flags.testFlag(FrameFlag::END_HEADERS);
1277 continuedFrames.clear();
1278 continuedFrames.push_back(std::move(inboundFrame));
1280 continuationExpected =
true;
1284 handleContinuedHEADERS();
1287void QHttp2Connection::handlePRIORITY()
1290 || inboundFrame.
type() == FrameType::HEADERS);
1292 const auto streamID = inboundFrame.
streamID();
1294 return connectionError(
PROTOCOL_ERROR,
"PRIORITY on 0x0 stream");
1296 if (isInvalidStream(streamID))
1301 const bool noErr = inboundFrame.
priority(&streamDependency, &
weight);
1305 const bool exclusive = streamDependency & 0x80000000;
1306 streamDependency &= ~0x80000000;
1314void QHttp2Connection::handleRST_STREAM()
1316 Q_ASSERT(inboundFrame.
type() == FrameType::RST_STREAM);
1322 const auto streamID = inboundFrame.
streamID();
1326 if (!(streamID & 0x1)) {
1334 if (streamID >= m_nextStreamID) {
1338 return connectionError(
PROTOCOL_ERROR,
"RST_STREAM on idle stream");
1343 if (QPointer<QHttp2Stream>
stream = m_streams[streamID])
1344 stream->handleRST_STREAM(inboundFrame);
1347void QHttp2Connection::handleSETTINGS()
1353 return connectionError(
PROTOCOL_ERROR,
"SETTINGS on invalid stream");
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;
1362 qCDebug(qHttp2ConnectionLog,
"[%p] Received SETTINGS frame",
this);
1368 const quint32 intVal = qFromBigEndian<quint32>(
src + 2);
1369 if (!acceptSetting(identifier, intVal)) {
1371 qCDebug(qHttp2ConnectionLog,
"[%p] Received an unacceptable setting, %u, %u",
this,
1378 qCDebug(qHttp2ConnectionLog,
"[%p] Sending SETTINGS ACK",
this);
1383void QHttp2Connection::handlePUSH_PROMISE()
1386 Q_ASSERT(inboundFrame.
type() == FrameType::PUSH_PROMISE);
1388 if (!pushPromiseEnabled && !waitingForSettingsACK) {
1391 return connectionError(
PROTOCOL_ERROR,
"unexpected PUSH_PROMISE frame");
1394 const auto streamID = inboundFrame.
streamID();
1396 return connectionError(
PROTOCOL_ERROR,
"PUSH_PROMISE with invalid associated stream (0x0)");
1410 return connectionError(
ENHANCE_YOUR_CALM,
"PUSH_PROMISE with invalid associated stream");
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");
1416 auto *
stream = createStreamInternal_impl(reservedID);
1418 return connectionError(
PROTOCOL_ERROR,
"PUSH_PROMISE with already active stream ID");
1419 m_lastIncomingStreamID = reservedID;
1422 if (!pushPromiseEnabled) {
1428 const bool endHeaders = inboundFrame.
flags().testFlag(FrameFlag::END_HEADERS);
1429 continuedFrames.clear();
1430 continuedFrames.push_back(std::move(inboundFrame));
1433 continuationExpected =
true;
1437 handleContinuedHEADERS();
1440void QHttp2Connection::handlePING()
1446 return connectionError(
PROTOCOL_ERROR,
"PING on invalid stream");
1448 if (inboundFrame.
flags() & FrameFlag::ACK) {
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);
1459 m_lastPingSignature.reset();
1469 frameWriter.
write(*getSocket());
1472void QHttp2Connection::handleGOAWAY()
1480 return connectionError(
PROTOCOL_ERROR,
"GOAWAY on invalid stream");
1483 quint32 lastStreamID = qFromBigEndian<quint32>(
src);
1484 const quint32 errorCode = qFromBigEndian<quint32>(
src + 4);
1486 if (!lastStreamID) {
1490 }
else if (!(lastStreamID & 0x1)) {
1492 return connectionError(
PROTOCOL_ERROR,
"GOAWAY with invalid last stream ID");
1493 }
else if (lastStreamID >= m_nextStreamID) {
1498 return connectionError(
PROTOCOL_ERROR,
"GOAWAY invalid stream/error code");
1507 for (
quint32 id = lastStreamID;
id < m_nextStreamID;
id += 2) {
1510 stream->finishWithError(errorCode,
"Received GOAWAY"_L1);
1518void QHttp2Connection::handleWINDOW_UPDATE()
1520 Q_ASSERT(inboundFrame.
type() == FrameType::WINDOW_UPDATE);
1523 const bool valid = delta && delta <=
quint32(std::numeric_limits<qint32>::max());
1524 const auto streamID = inboundFrame.
streamID();
1526 qCDebug(qHttp2ConnectionLog(),
"[%p] Received WINDOW_UPDATE, stream %d, delta %d",
this,
1531 return connectionError(
PROTOCOL_ERROR,
"WINDOW_UPDATE invalid delta");
1532 sessionSendWindowSize =
sum;
1533 for (
auto &
stream : m_streams) {
1537 if (
stream->isUploadingDATA() && !
stream->isUploadBlocked())
1545 qCDebug(qHttp2ConnectionLog,
"[%p] Received WINDOW_UPDATE on closed stream %d",
this,
1549 stream->handleWINDOW_UPDATE(inboundFrame);
1553void QHttp2Connection::handleCONTINUATION()
1555 Q_ASSERT(inboundFrame.
type() == FrameType::CONTINUATION);
1556 if (continuedFrames.empty())
1558 "CONTINUATION without a preceding HEADERS or PUSH_PROMISE");
1560 if (inboundFrame.
streamID() != continuedFrames.front().streamID())
1561 return connectionError(
PROTOCOL_ERROR,
"CONTINUATION on invalid stream");
1563 const bool endHeaders = inboundFrame.
flags().testFlag(FrameFlag::END_HEADERS);
1564 continuedFrames.push_back(std::move(inboundFrame));
1569 continuationExpected =
false;
1570 handleContinuedHEADERS();
1573void QHttp2Connection::handleContinuedHEADERS()
1578 Q_ASSERT(!continuedFrames.empty());
1579 const auto firstFrameType = continuedFrames[0].type();
1580 Q_ASSERT(firstFrameType == FrameType::HEADERS || firstFrameType == FrameType::PUSH_PROMISE);
1582 const auto streamID = continuedFrames[0].streamID();
1584 const auto streamIt = m_streams.
constFind(streamID);
1585 if (firstFrameType == FrameType::HEADERS) {
1586 if (streamIt != m_streams.
cend()) {
1596 "HEADERS on invalid stream"_L1);
1607 const bool hasHeaderFields = !hpackBlock.empty();
1608 if (hasHeaderFields) {
1609 HPack::BitIStream inputStream{ hpackBlock.data(), hpackBlock.data() + hpackBlock.size() };
1613 if (firstFrameType == FrameType::PUSH_PROMISE) {
1622 if (streamIt != m_streams.
cend())
1628 constexpr auto hpackBlockHasContent = [](
const auto &
c) {
return c.hpackBlockSize() > 0; };
1629 const bool anyHpackBlock = std::any_of(continuedFrames.cbegin(), continuedFrames.cend(),
1630 hpackBlockHasContent);
1635 if (streamIt == m_streams.
cend())
1638 switch (firstFrameType) {
1639 case FrameType::HEADERS:
1640 streamIt.value()->handleHEADERS(continuedFrames[0].
flags(), decoder.
decodedHeader());
1642 case FrameType::PUSH_PROMISE: {
1646 if (m_promisedStreams.
contains(*promiseKey))
1648 const auto promiseID = qFromBigEndian<quint32>(continuedFrames[0].dataBegin());
1650 stream->transitionState(QHttp2Stream::StateTransition::CloseLocal);
1653 m_promisedStreams.
emplace(*promiseKey, promiseID);
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) {
1673 case Settings::INITIAL_WINDOW_SIZE_ID: {
1674 qCDebug(qHttp2ConnectionLog,
"[%p] Received SETTINGS INITIAL_WINDOW_SIZE %d",
this,
1678 if (newValue >
quint32(std::numeric_limits<qint32>::max())) {
1683 const qint32 delta =
qint32(newValue) - streamInitialSendWindowSize;
1684 streamInitialSendWindowSize =
qint32(newValue);
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)) {
1695 "SETTINGS window overflow"_L1);
1699 if (delta > 0 &&
stream->isUploadingDATA() && !
stream->isUploadBlocked()) {
1706 case Settings::MAX_CONCURRENT_STREAMS_ID: {
1707 qCDebug(qHttp2ConnectionLog,
"[%p] Received SETTINGS MAX_CONCURRENT_STREAMS %d",
this,
1709 m_maxConcurrentStreams = newValue;
1712 case Settings::MAX_FRAME_SIZE_ID: {
1713 qCDebug(qHttp2ConnectionLog,
"[%p] Received SETTINGS MAX_FRAME_SIZE %d",
this, newValue);
1715 connectionError(
PROTOCOL_ERROR,
"SETTINGS max frame size is out of range");
1718 maxFrameSize = newValue;
1721 case Settings::MAX_HEADER_LIST_SIZE_ID: {
1722 qCDebug(qHttp2ConnectionLog,
"[%p] Received SETTINGS MAX_HEADER_LIST_SIZE %d",
this,
1727 m_maxHeaderListSize = newValue;
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");
1736 if (m_connectionType == Type::Client) {
1737 if (newValue == 1) {
1738 connectionError(
PROTOCOL_ERROR,
"SETTINGS server sent ENABLE_PUSH=1");
1742 pushPromiseEnabled = newValue;
1752#include "moc_qhttp2connection_p.cpp"
IOBluetoothDevice * device
const HttpHeader & decodedHeader() const
bool decodeHeaderFields(class BitIStream &inputStream)
void setCompressStrings(bool compress)
void setMaxDynamicTableSize(quint32 size)
FrameStatus read(QIODevice &socket)
bool writeHEADERS(QIODevice &socket, quint32 sizeLimit)
void addFlag(FrameFlag flag)
void append(ValueType val)
void setOutboundFrame(Frame &&newFrame)
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.
void append(const QByteDataBuffer &other)
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.
const_iterator cbegin() const noexcept
qsizetype size() const noexcept
Returns the number of items in the hash.
const_iterator constFind(const Key &key) const noexcept
const_iterator constEnd() const noexcept
Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the ...
iterator emplace(const Key &key, Args &&... args)
bool contains(const Key &key) const noexcept
Returns true if the hash contains an item with the key; otherwise returns false.
T value(const Key &key) const noexcept
iterator end() noexcept
Returns an \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the last ...
const_iterator cend() const noexcept
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.
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.
@ MaxConcurrentStreamsReached
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...
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.
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
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 pos() const
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.
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
void destroyed(QObject *=nullptr)
This signal is emitted immediately before the object obj is destroyed, after any instances of QPointe...
void deleteLater()
\threadsafe
\inmodule QtCore \reentrant
quint32 generate()
Generates a 32-bit random quantity and returns it.
\macro QT_RESTRICTED_CAST_FROM_ASCII
QSet< QString >::iterator it
void newState(QList< State > &states, const char *token, const char *lexem, bool pre)
std::vector< HeaderField > HttpHeader
std::optional< QUrl > makePromiseKeyUrl(const HttpHeader &requestHeader)
std::vector< uchar > assemble_hpack_block(const std::vector< Frame > &frames)
const char Http2clientPreface[clientPrefaceLength]
const quint32 lastValidStreamID((quint32(1)<< 31) - 1)
@ defaultSessionWindowSize
Frame configurationToSettingsFrame(const QHttp2Configuration &config)
void qt_error(quint32 errorCode, QNetworkReply::NetworkError &error, QString &errorMessage)
Combined button and popup list for selecting options.
DBusConnection const char DBusError * error
DBusConnection * connection
#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)
GLenum GLsizei GLuint GLint * bytesWritten
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint GLuint GLfloat weight
GLuint GLsizei const GLchar * message
GLint GLint GLint GLint GLint GLint GLint GLbitfield mask
#define qUtf16Printable(string)
ReturnedValue read(const char *data)
myObject disconnect()
[26]
bool priority(quint32 *streamID=nullptr, uchar *weight=nullptr) const
const uchar * dataBegin() const
quint32 payloadSize() const
std::vector< uchar > buffer