Send Stream Cancellation QPACK instruction.
According to
https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#name-abandonment-of-a-stream,
this should be done when a stream reset is received before the end of a stream
or before all header blocks are processed on that stream, or when reading of a
stream is abandoned.
gfe-relnote: n/a, change to QUIC v99-only code. Protected by existing disabled gfe2_reloadable_flag_quic_enable_version_99.
PiperOrigin-RevId: 282861038
Change-Id: Ibf6f69e29f98022e0eebf89676783fa3ce26541a
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index 27717ab..b37246a 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -32,6 +32,7 @@
#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
#include "net/third_party/quiche/src/quic/test_tools/qpack/qpack_encoder_peer.h"
#include "net/third_party/quiche/src/quic/test_tools/qpack/qpack_header_table_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/qpack/qpack_test_utils.h"
#include "net/third_party/quiche/src/quic/test_tools/quic_config_peer.h"
#include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h"
#include "net/third_party/quiche/src/quic/test_tools/quic_flow_controller_peer.h"
@@ -2025,6 +2026,14 @@
}
TEST_P(QuicSpdySessionTestServer, DonotRetransmitDataOfClosedStreams) {
+ // Resetting a stream will send a QPACK Stream Cancellation instruction on the
+ // decoder stream. For simplicity, ignore writes on this stream.
+ NoopQpackStreamSenderDelegate qpack_stream_sender_delegate;
+ if (VersionUsesHttp3(transport_version())) {
+ session_.qpack_decoder()->set_qpack_stream_sender_delegate(
+ &qpack_stream_sender_delegate);
+ }
+
InSequence s;
TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
index 4c5e642..d25f99d 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -680,9 +680,15 @@
void QuicSpdyStream::OnStreamReset(const QuicRstStreamFrame& frame) {
if (frame.error_code != QUIC_STREAM_NO_ERROR) {
+ if (VersionUsesHttp3(transport_version()) && !fin_received() &&
+ spdy_session_->qpack_decoder()) {
+ spdy_session_->qpack_decoder()->OnStreamReset(id());
+ }
+
QuicStream::OnStreamReset(frame);
return;
}
+
QUIC_DVLOG(1) << ENDPOINT
<< "Received QUIC_STREAM_NO_ERROR, not discarding response";
set_rst_received(true);
@@ -691,6 +697,15 @@
CloseWriteSide();
}
+void QuicSpdyStream::Reset(QuicRstStreamErrorCode error) {
+ if (VersionUsesHttp3(transport_version()) && !fin_received() &&
+ spdy_session_->qpack_decoder()) {
+ spdy_session_->qpack_decoder()->OnStreamReset(id());
+ }
+
+ QuicStream::Reset(error);
+}
+
void QuicSpdyStream::OnDataAvailable() {
if (!VersionUsesHttp3(transport_version())) {
// Sequencer must be blocked until headers are consumed.
diff --git a/quic/core/http/quic_spdy_stream.h b/quic/core/http/quic_spdy_stream.h
index e315dcc..8610dbb 100644
--- a/quic/core/http/quic_spdy_stream.h
+++ b/quic/core/http/quic_spdy_stream.h
@@ -102,6 +102,8 @@
// QUIC_STREAM_NO_ERROR.
void OnStreamReset(const QuicRstStreamFrame& frame) override;
+ void Reset(QuicRstStreamErrorCode error) override;
+
// Called by the sequencer when new data is available. Decodes the data and
// calls OnBodyAvailable() to pass to the upper layer.
void OnDataAvailable() override;
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc
index 73177dc..8def703 100644
--- a/quic/core/http/quic_spdy_stream_test.cc
+++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -2522,6 +2522,43 @@
EXPECT_EQ(0u, stream_->sequencer()->NumBytesConsumed());
}
+// Stream Cancellation instruction is sent on QPACK decoder stream
+// when stream is reset.
+TEST_P(QuicSpdyStreamTest, StreamCancellationWhenStreamReset) {
+ if (!UsesHttp3()) {
+ return;
+ }
+
+ Initialize(kShouldProcessData);
+
+ auto qpack_decoder_stream =
+ QuicSpdySessionPeer::GetQpackDecoderSendStream(session_.get());
+ EXPECT_CALL(*session_, WritevData(qpack_decoder_stream,
+ qpack_decoder_stream->id(), 1, 1, _));
+ EXPECT_CALL(*session_,
+ SendRstStream(stream_->id(), QUIC_STREAM_CANCELLED, 0));
+
+ stream_->Reset(QUIC_STREAM_CANCELLED);
+}
+
+// Stream Cancellation instruction is sent on QPACK decoder stream
+// when RESET_STREAM frame is received.
+TEST_P(QuicSpdyStreamTest, StreamCancellationOnResetReceived) {
+ if (!UsesHttp3()) {
+ return;
+ }
+
+ Initialize(kShouldProcessData);
+
+ auto qpack_decoder_stream =
+ QuicSpdySessionPeer::GetQpackDecoderSendStream(session_.get());
+ EXPECT_CALL(*session_, WritevData(qpack_decoder_stream,
+ qpack_decoder_stream->id(), 1, 1, _));
+
+ stream_->OnStreamReset(QuicRstStreamFrame(
+ kInvalidControlFrameId, stream_->id(), QUIC_STREAM_CANCELLED, 0));
+}
+
} // namespace
} // namespace test
} // namespace quic
diff --git a/quic/core/qpack/qpack_decoder.cc b/quic/core/qpack/qpack_decoder.cc
index 4f39e3b..3ae6bce 100644
--- a/quic/core/qpack/qpack_decoder.cc
+++ b/quic/core/qpack/qpack_decoder.cc
@@ -27,10 +27,10 @@
QpackDecoder::~QpackDecoder() {}
void QpackDecoder::OnStreamReset(QuicStreamId stream_id) {
- // TODO(bnc): SendStreamCancellation should not be called if maximum dynamic
- // table capacity is zero.
- decoder_stream_sender_.SendStreamCancellation(stream_id);
- decoder_stream_sender_.Flush();
+ if (header_table_.maximum_dynamic_table_capacity() > 0) {
+ decoder_stream_sender_.SendStreamCancellation(stream_id);
+ decoder_stream_sender_.Flush();
+ }
}
bool QpackDecoder::OnStreamBlocked(QuicStreamId stream_id) {
diff --git a/quic/core/qpack/qpack_header_table.h b/quic/core/qpack/qpack_header_table.h
index 1b8020f..dd5ca3b 100644
--- a/quic/core/qpack/qpack_header_table.h
+++ b/quic/core/qpack/qpack_header_table.h
@@ -98,6 +98,11 @@
// This method must only be called at most once.
void SetMaximumDynamicTableCapacity(uint64_t maximum_dynamic_table_capacity);
+ // Get |maximum_dynamic_table_capacity_|.
+ uint64_t maximum_dynamic_table_capacity() const {
+ return maximum_dynamic_table_capacity_;
+ }
+
// Register an observer to be notified when inserted_entry_count() reaches
// |required_insert_count|. After the notification, |observer| automatically
// gets unregistered. Each observer must only be registered at most once.
diff --git a/quic/tools/quic_simple_server_stream_test.cc b/quic/tools/quic_simple_server_stream_test.cc
index 587bc28..50ca84f 100644
--- a/quic/tools/quic_simple_server_stream_test.cc
+++ b/quic/tools/quic_simple_server_stream_test.cc
@@ -633,6 +633,16 @@
EXPECT_FALSE(stream_->reading_stopped());
EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_NO_ERROR, _)).Times(0);
+ if (VersionUsesHttp3(connection_->transport_version())) {
+ // Unidirectional stream type and then a Stream Cancellation instruction is
+ // sent on the QPACK decoder stream. Ignore these writes without any
+ // assumption on their number or size.
+ auto* qpack_decoder_stream =
+ QuicSpdySessionPeer::GetQpackDecoderSendStream(&session_);
+ EXPECT_CALL(session_, WritevData(qpack_decoder_stream,
+ qpack_decoder_stream->id(), _, _, _))
+ .Times(AnyNumber());
+ }
EXPECT_CALL(session_, SendRstStream(_, QUIC_RST_ACKNOWLEDGEMENT, _)).Times(1);
QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream_->id(),
QUIC_STREAM_CANCELLED, 1234);