Http2 protocol handler: Fix handling of MAX_HEADER_TABLE_SIZE setting

Like the patch in dev, but for 6.8 and below

We need to, following the RFC, send a Dynamic Table Size Update using
HPack once we have acknowledged the setting as long as the new size is
smaller, or if we decide to use the new, larger size.

It's further complicated by the fact that we need to send this update on
the next 'field block' (anything with headers in it), and we may have
multiple SETTING frames come in, and we need to then acknowledge the
_smallest_ one as well as the _final_ one. This is so the decoder on the
peer's side can know that we have set the smallest size, and trimmed our
tables thusly, before going to the larger size.

This could, for example, be used to clear the table.

Fixes: QTBUG-132277
Pick-to: 6.5
Change-Id: I95f006248dfcbc7952c7e184f92d8859270e9e70
Reviewed-by: Mate Barany <mate.barany@qt.io>
This commit is contained in:
Mårten Nordheim 2024-12-18 15:27:58 +01:00
parent 42a62db6f9
commit a428e89562
2 changed files with 26 additions and 1 deletions

View File

@ -441,6 +441,16 @@ bool QHttp2ProtocolHandler::sendHEADERS(Stream &stream)
// Compress in-place:
BitOStream outputStream(frameWriter.outboundFrame().buffer);
// Possibly perform and notify of dynamic table size update:
for (auto &maybePendingTableSizeUpdate : pendingTableSizeUpdates) {
if (!maybePendingTableSizeUpdate)
break; // They are ordered, so if the first one is null, the other one is too.
encoder.setMaxDynamicTableSize(*maybePendingTableSizeUpdate);
encoder.encodeSizeUpdate(outputStream, *maybePendingTableSizeUpdate);
maybePendingTableSizeUpdate.reset();
}
if (!encoder.encodeRequest(outputStream, headers))
return false;
@ -989,7 +999,15 @@ bool QHttp2ProtocolHandler::acceptSetting(Http2::Settings identifier, quint32 ne
connectionError(PROTOCOL_ERROR, "SETTINGS invalid table size");
return false;
}
encoder.setMaxDynamicTableSize(newValue);
if (!pendingTableSizeUpdates[0] && encoder.dynamicTableCapacity() == newValue)
return true; // No change, no need to update.
if (pendingTableSizeUpdates[0].value_or(std::numeric_limits<quint32>::max()) >= newValue) {
pendingTableSizeUpdates[0] = newValue;
pendingTableSizeUpdates[1].reset(); // 0 is the latest _and_ smallest, so we don't need 1
} else {
pendingTableSizeUpdates[1] = newValue; // newValue was larger than 0, so it goes to 1
}
}
if (identifier == Settings::INITIAL_WINDOW_SIZE_ID) {

View File

@ -127,6 +127,13 @@ private:
HPack::Decoder decoder;
HPack::Encoder encoder;
// If we receive SETTINGS_HEADER_TABLE_SIZE in a SETTINGS frame we have to perform a dynamic
// table size update on the _next_ HEADER block we send.
// Because this only happens on the next block we may have multiple pending updates, so we must
// notify of the _smallest_ one followed by the _final_ one. We keep them sorted in that order.
// @future: keep in mind if we add support for sending PUSH_PROMISE because it is a HEADER block
std::array<std::optional<quint32>, 2> pendingTableSizeUpdates;
QHash<QObject *, int> streamIDs;
QHash<quint32, Stream> activeStreams;
std::deque<quint32> suspendedStreams[3]; // 3 for priorities: High, Normal, Low.