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