Enforce limit on number of blocked streams in QPACK decoder.
gfe-relnote: n/a, change to QUIC v99-only code. Protected by existing disabled gfe2_reloadable_flag_quic_enable_version_99.
PiperOrigin-RevId: 264688336
Change-Id: Iab1e97a9f31ef95d8dfa05ca8439bf066913b5ba
diff --git a/quic/core/http/http_constants.h b/quic/core/http/http_constants.h
index 84d0abd..9c2c6b5 100644
--- a/quic/core/http/http_constants.h
+++ b/quic/core/http/http_constants.h
@@ -36,6 +36,10 @@
// SETTINGS_QPACK_MAX_TABLE_CAPACITY.
const QuicByteCount kDefaultQpackMaxDynamicTableCapacity = 64 * 1024; // 64 KB
+// Default limit on number of blocked streams, communicated via
+// SETTINGS_QPACK_BLOCKED_STREAMS.
+const uint64_t kDefaultMaximumBlockedStreams = 100;
+
} // namespace quic
#endif // QUICHE_QUIC_CORE_HTTP_HTTP_CONSTANTS_H_
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc
index 619c881..29c6d9d 100644
--- a/quic/core/http/quic_spdy_session.cc
+++ b/quic/core/http/quic_spdy_session.cc
@@ -380,13 +380,11 @@
qpack_encoder_ = QuicMakeUnique<QpackEncoder>(this);
qpack_decoder_ =
QuicMakeUnique<QpackDecoder>(kDefaultQpackMaxDynamicTableCapacity,
- /* maximum_blocked_streams = */ 0, this);
+ kDefaultMaximumBlockedStreams, this);
MaybeInitializeHttp3UnidirectionalStreams();
- // TODO(b/112770235): Set sensible limit on maximum number of blocked
- // streams.
// TODO(b/112770235): Send SETTINGS_QPACK_MAX_TABLE_CAPACITY with value
// kDefaultQpackMaxDynamicTableCapacity, and SETTINGS_QPACK_BLOCKED_STREAMS
- // with limit on maximum number of blocked streams.
+ // with value kDefaultMaximumBlockedStreams.
}
spdy_framer_visitor_->set_max_header_list_size(max_inbound_header_list_size_);
diff --git a/quic/core/qpack/qpack_decoder.cc b/quic/core/qpack/qpack_decoder.cc
index 7fac1f2..b20cbdf 100644
--- a/quic/core/qpack/qpack_decoder.cc
+++ b/quic/core/qpack/qpack_decoder.cc
@@ -12,10 +12,11 @@
QpackDecoder::QpackDecoder(
uint64_t maximum_dynamic_table_capacity,
- uint64_t /* maximum_blocked_streams */,
+ uint64_t maximum_blocked_streams,
EncoderStreamErrorDelegate* encoder_stream_error_delegate)
: encoder_stream_error_delegate_(encoder_stream_error_delegate),
- encoder_stream_receiver_(this) {
+ encoder_stream_receiver_(this),
+ maximum_blocked_streams_(maximum_blocked_streams) {
DCHECK(encoder_stream_error_delegate_);
header_table_.SetMaximumDynamicTableCapacity(maximum_dynamic_table_capacity);
@@ -29,6 +30,17 @@
decoder_stream_sender_.SendStreamCancellation(stream_id);
}
+bool QpackDecoder::OnStreamBlocked(QuicStreamId stream_id) {
+ auto result = blocked_streams_.insert(stream_id);
+ DCHECK(result.second);
+ return blocked_streams_.size() <= maximum_blocked_streams_;
+}
+
+void QpackDecoder::OnStreamUnblocked(QuicStreamId stream_id) {
+ size_t result = blocked_streams_.erase(stream_id);
+ DCHECK_EQ(1u, result);
+}
+
void QpackDecoder::OnInsertWithNameReference(bool is_static,
uint64_t name_index,
QuicStringPiece value) {
@@ -117,7 +129,7 @@
QuicStreamId stream_id,
QpackProgressiveDecoder::HeadersHandlerInterface* handler) {
return QuicMakeUnique<QpackProgressiveDecoder>(
- stream_id, &header_table_, &decoder_stream_sender_, handler);
+ stream_id, this, &header_table_, &decoder_stream_sender_, handler);
}
} // namespace quic
diff --git a/quic/core/qpack/qpack_decoder.h b/quic/core/qpack/qpack_decoder.h
index 1ec2d39..87d650f 100644
--- a/quic/core/qpack/qpack_decoder.h
+++ b/quic/core/qpack/qpack_decoder.h
@@ -22,7 +22,8 @@
// This class vends a new QpackProgressiveDecoder instance for each new header
// list to be encoded.
class QUIC_EXPORT_PRIVATE QpackDecoder
- : public QpackEncoderStreamReceiver::Delegate {
+ : public QpackEncoderStreamReceiver::Delegate,
+ public QpackProgressiveDecoder::BlockedStreamLimitEnforcer {
public:
// Interface for receiving notification that an error has occurred on the
// encoder stream. This MUST be treated as a connection error of type
@@ -57,6 +58,10 @@
// using the FIN bit.
void OnStreamReset(QuicStreamId stream_id);
+ // QpackProgressiveDecoder::BlockedStreamLimitEnforcer implementation.
+ bool OnStreamBlocked(QuicStreamId stream_id) override;
+ void OnStreamUnblocked(QuicStreamId stream_id) override;
+
// Factory method to create a QpackProgressiveDecoder for decoding a header
// block. |handler| must remain valid until the returned
// QpackProgressiveDecoder instance is destroyed or the decoder calls
@@ -89,6 +94,8 @@
QpackEncoderStreamReceiver encoder_stream_receiver_;
QpackDecoderStreamSender decoder_stream_sender_;
QpackHeaderTable header_table_;
+ std::set<QuicStreamId> blocked_streams_;
+ const uint64_t maximum_blocked_streams_;
};
} // namespace quic
diff --git a/quic/core/qpack/qpack_decoder_test.cc b/quic/core/qpack/qpack_decoder_test.cc
index 556053b..0a4e5e2 100644
--- a/quic/core/qpack/qpack_decoder_test.cc
+++ b/quic/core/qpack/qpack_decoder_test.cc
@@ -26,16 +26,15 @@
// Header Acknowledgement decoder stream instruction with stream_id = 1.
const char* const kHeaderAcknowledgement = "\x81";
-// TODO(b/112770235) Change this constant, enforce the limit and add tests.
-const uint64_t kMaximumBlockedStreams = 0;
+const uint64_t kMaximumDynamicTableCapacity = 1024;
+const uint64_t kMaximumBlockedStreams = 1;
class QpackDecoderTest : public QuicTestWithParam<FragmentMode> {
protected:
QpackDecoderTest()
- : qpack_decoder_(
- /* maximum_dynamic_table_capacity = */ 1024,
- kMaximumBlockedStreams,
- &encoder_stream_error_delegate_),
+ : qpack_decoder_(kMaximumDynamicTableCapacity,
+ kMaximumBlockedStreams,
+ &encoder_stream_error_delegate_),
fragment_mode_(GetParam()) {
qpack_decoder_.set_qpack_stream_sender_delegate(
&decoder_stream_sender_delegate_);
@@ -47,10 +46,14 @@
qpack_decoder_.encoder_stream_receiver()->Decode(data);
}
+ std::unique_ptr<QpackProgressiveDecoder> CreateProgressiveDecoder(
+ QuicStreamId stream_id) {
+ return qpack_decoder_.CreateProgressiveDecoder(stream_id, &handler_);
+ }
+
// Set up |progressive_decoder_|.
void StartDecoding() {
- progressive_decoder_ =
- qpack_decoder_.CreateProgressiveDecoder(/* stream_id = */ 1, &handler_);
+ progressive_decoder_ = CreateProgressiveDecoder(/* stream_id = */ 1);
}
// Pass header block data to QpackProgressiveDecoder::Decode()
@@ -813,6 +816,21 @@
DecodeEncoderStreamData(QuicTextUtils::HexDecode("6294e70362617a"));
}
+TEST_P(QpackDecoderTest, TooManyBlockedStreams) {
+ // Required Insert Count 1 and Delta Base 0.
+ // Without any dynamic table entries received, decoding is blocked.
+ std::string data = QuicTextUtils::HexDecode("0200");
+
+ auto progressive_decoder1 = CreateProgressiveDecoder(/* stream_id = */ 1);
+ progressive_decoder1->Decode(data);
+
+ EXPECT_CALL(handler_, OnDecodingErrorDetected(Eq(
+ "Limit on number of blocked streams exceeded.")));
+
+ auto progressive_decoder2 = CreateProgressiveDecoder(/* stream_id = */ 2);
+ progressive_decoder2->Decode(data);
+}
+
} // namespace
} // namespace test
} // namespace quic
diff --git a/quic/core/qpack/qpack_progressive_decoder.cc b/quic/core/qpack/qpack_progressive_decoder.cc
index e5f7f09..51d98ff 100644
--- a/quic/core/qpack/qpack_progressive_decoder.cc
+++ b/quic/core/qpack/qpack_progressive_decoder.cc
@@ -17,6 +17,7 @@
QpackProgressiveDecoder::QpackProgressiveDecoder(
QuicStreamId stream_id,
+ BlockedStreamLimitEnforcer* enforcer,
QpackHeaderTable* header_table,
QpackDecoderStreamSender* decoder_stream_sender,
HeadersHandlerInterface* handler)
@@ -24,6 +25,7 @@
prefix_decoder_(
QuicMakeUnique<QpackInstructionDecoder>(QpackPrefixLanguage(), this)),
instruction_decoder_(QpackRequestStreamLanguage(), this),
+ enforcer_(enforcer),
header_table_(header_table),
decoder_stream_sender_(decoder_stream_sender),
handler_(handler),
@@ -117,6 +119,7 @@
}
blocked_ = false;
+ enforcer_->OnStreamUnblocked(stream_id_);
if (!decoding_) {
FinishDecoding();
@@ -287,6 +290,10 @@
if (required_insert_count_ > header_table_->inserted_entry_count()) {
blocked_ = true;
+ if (!enforcer_->OnStreamBlocked(stream_id_)) {
+ OnError("Limit on number of blocked streams exceeded.");
+ return false;
+ }
header_table_->RegisterObserver(this, required_insert_count_);
}
diff --git a/quic/core/qpack/qpack_progressive_decoder.h b/quic/core/qpack/qpack_progressive_decoder.h
index 6d2e992..5c116b1 100644
--- a/quic/core/qpack/qpack_progressive_decoder.h
+++ b/quic/core/qpack/qpack_progressive_decoder.h
@@ -49,8 +49,26 @@
virtual void OnDecodingErrorDetected(QuicStringPiece error_message) = 0;
};
+ // Interface for keeping track of blocked streams for the purpose of enforcing
+ // the limit communicated to peer via QPACK_BLOCKED_STREAMS settings.
+ class QUIC_EXPORT_PRIVATE BlockedStreamLimitEnforcer {
+ public:
+ virtual ~BlockedStreamLimitEnforcer() {}
+
+ // Called when the stream becomes blocked. Returns true if allowed. Returns
+ // false if limit is violated, in which case QpackProgressiveDecoder signals
+ // an error.
+ // Stream must not be already blocked.
+ virtual bool OnStreamBlocked(QuicStreamId stream_id) = 0;
+
+ // Called when the stream becomes unblocked.
+ // Stream must be blocked.
+ virtual void OnStreamUnblocked(QuicStreamId stream_id) = 0;
+ };
+
QpackProgressiveDecoder() = delete;
QpackProgressiveDecoder(QuicStreamId stream_id,
+ BlockedStreamLimitEnforcer* enforcer,
QpackHeaderTable* header_table,
QpackDecoderStreamSender* decoder_stream_sender,
HeadersHandlerInterface* handler);
@@ -97,6 +115,7 @@
std::unique_ptr<QpackInstructionDecoder> prefix_decoder_;
QpackInstructionDecoder instruction_decoder_;
+ BlockedStreamLimitEnforcer* const enforcer_;
QpackHeaderTable* const header_table_;
QpackDecoderStreamSender* const decoder_stream_sender_;
HeadersHandlerInterface* const handler_;
diff --git a/quic/core/qpack/qpack_progressive_decoder_test.cc b/quic/core/qpack/qpack_progressive_decoder_test.cc
new file mode 100644
index 0000000..ccc7436
--- /dev/null
+++ b/quic/core/qpack/qpack_progressive_decoder_test.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_progressive_decoder.h"
+
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder_test_utils.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_test_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+
+using ::testing::Eq;
+using ::testing::Return;
+using ::testing::StrictMock;
+
+namespace quic {
+namespace test {
+namespace {
+
+const uint64_t kMaximumDynamicTableCapacityForTesting = 1024 * 1024;
+const QuicStreamId kStreamId = 0;
+// Header Acknowledgement decoder stream instruction with stream_id = 0.
+const char* const kHeaderAcknowledgement = "\x80";
+
+class MockEnforcer
+ : public QpackProgressiveDecoder::BlockedStreamLimitEnforcer {
+ public:
+ ~MockEnforcer() override = default;
+
+ MOCK_METHOD(bool, OnStreamBlocked, (QuicStreamId stream_id));
+ MOCK_METHOD(void, OnStreamUnblocked, (QuicStreamId stream_id));
+};
+
+class QpackProgressiveDecoderTest : public QuicTest {
+ protected:
+ QpackProgressiveDecoderTest()
+ : progressive_decoder_(kStreamId,
+ &enforcer_,
+ &header_table_,
+ &decoder_stream_sender_,
+ &headers_handler_) {
+ decoder_stream_sender_.set_qpack_stream_sender_delegate(
+ &decoder_stream_sender_delegate_);
+ header_table_.SetMaximumDynamicTableCapacity(
+ kMaximumDynamicTableCapacityForTesting);
+ }
+ ~QpackProgressiveDecoderTest() override = default;
+
+ QpackProgressiveDecoder progressive_decoder_;
+ StrictMock<MockEnforcer> enforcer_;
+ QpackHeaderTable header_table_;
+ QpackDecoderStreamSender decoder_stream_sender_;
+ StrictMock<MockQpackStreamSenderDelegate> decoder_stream_sender_delegate_;
+ StrictMock<MockHeadersHandler> headers_handler_;
+};
+
+TEST_F(QpackProgressiveDecoderTest, Literal) {
+ EXPECT_CALL(headers_handler_, OnHeaderDecoded(Eq("foo"), Eq("bar")));
+ progressive_decoder_.Decode(QuicTextUtils::HexDecode("000023666f6f03626172"));
+
+ EXPECT_CALL(headers_handler_, OnDecodingCompleted());
+ progressive_decoder_.EndHeaderBlock();
+}
+
+TEST_F(QpackProgressiveDecoderTest, DynamicTableSynchronous) {
+ EXPECT_TRUE(header_table_.InsertEntry("foo", "bar"));
+
+ EXPECT_CALL(headers_handler_, OnHeaderDecoded(Eq("foo"), Eq("bar")));
+ progressive_decoder_.Decode(QuicTextUtils::HexDecode(
+ "0200" // Required Insert Count 1 and Delta Base 0.
+ "80")); // Dynamic table entry with relative index 0,
+ // absolute index 0.
+
+ EXPECT_CALL(headers_handler_, OnDecodingCompleted());
+ EXPECT_CALL(decoder_stream_sender_delegate_,
+ WriteStreamData(Eq(kHeaderAcknowledgement)));
+ progressive_decoder_.EndHeaderBlock();
+}
+
+TEST_F(QpackProgressiveDecoderTest, DynamicTableBlocked) {
+ EXPECT_CALL(enforcer_, OnStreamBlocked(kStreamId)).WillOnce(Return(true));
+ progressive_decoder_.Decode(QuicTextUtils::HexDecode(
+ "0200" // Required Insert Count 1 and Delta Base 0.
+ "80")); // Dynamic table entry with relative index 0,
+ // absolute index 0.
+ progressive_decoder_.EndHeaderBlock();
+
+ EXPECT_CALL(enforcer_, OnStreamUnblocked(kStreamId));
+ EXPECT_CALL(headers_handler_, OnHeaderDecoded(Eq("foo"), Eq("bar")));
+ EXPECT_CALL(headers_handler_, OnDecodingCompleted());
+ EXPECT_CALL(decoder_stream_sender_delegate_,
+ WriteStreamData(Eq(kHeaderAcknowledgement)));
+
+ EXPECT_TRUE(header_table_.InsertEntry("foo", "bar"));
+}
+
+TEST_F(QpackProgressiveDecoderTest, TooManyBlockedStreams) {
+ EXPECT_CALL(enforcer_, OnStreamBlocked(kStreamId)).WillOnce(Return(false));
+ EXPECT_CALL(
+ headers_handler_,
+ OnDecodingErrorDetected("Limit on number of blocked streams exceeded."));
+
+ // Required Insert Count 1.
+ progressive_decoder_.Decode(QuicTextUtils::HexDecode("0200"));
+}
+
+} // namespace
+} // namespace test
+} // namespace quic