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);