Adds optional RST_STREAM NO_ERROR behavior after sending a fin to an incomplete request as a server.

PiperOrigin-RevId: 410264655
diff --git a/http2/adapter/oghttp2_adapter_test.cc b/http2/adapter/oghttp2_adapter_test.cc
index efe0327..171f0b2 100644
--- a/http2/adapter/oghttp2_adapter_test.cc
+++ b/http2/adapter/oghttp2_adapter_test.cc
@@ -1677,7 +1677,7 @@
   EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
   EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5));
   EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0));
-  // BUG: Should send RST_STREAM NO_ERROR as well.
+  // RST_STREAM NO_ERROR option is disabled.
 
   int send_result = adapter->Send();
   EXPECT_EQ(0, send_result);
@@ -1687,6 +1687,62 @@
   EXPECT_FALSE(adapter->want_write());
 }
 
+TEST(OgHttp2AdapterServerTest,
+     IncompleteRequestWithServerResponseRstStreamEnabled) {
+  DataSavingVisitor visitor;
+  OgHttp2Adapter::Options options{.perspective = Perspective::kServer,
+                                  .rst_stream_no_error_when_incomplete = true};
+  auto adapter = OgHttp2Adapter::Create(visitor, options);
+  EXPECT_FALSE(adapter->want_write());
+
+  const std::string frames = TestFrameSequence()
+                                 .ClientPreface()
+                                 .Headers(1,
+                                          {{":method", "GET"},
+                                           {":scheme", "https"},
+                                           {":authority", "example.com"},
+                                           {":path", "/this/is/request/one"}},
+                                          /*fin=*/false)
+                                 .Serialize();
+  testing::InSequence s;
+
+  // Client preface (empty SETTINGS)
+  EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+  EXPECT_CALL(visitor, OnSettingsStart());
+  EXPECT_CALL(visitor, OnSettingsEnd());
+  // Stream 1
+  EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4));
+  EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+  EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4);
+  EXPECT_CALL(visitor, OnEndHeadersForStream(1));
+
+  const int64_t result = adapter->ProcessBytes(frames);
+  EXPECT_EQ(frames.size(), static_cast<size_t>(result));
+
+  int submit_result =
+      adapter->SubmitResponse(1, ToHeaders({{":status", "200"}}), nullptr);
+  EXPECT_EQ(submit_result, 0);
+  EXPECT_TRUE(adapter->want_write());
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, 4, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(RST_STREAM, 1, 4, 0x0, 0));
+  EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR));
+
+  int send_result = adapter->Send();
+  EXPECT_EQ(0, send_result);
+  EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+                                            spdy::SpdyFrameType::SETTINGS,
+                                            spdy::SpdyFrameType::HEADERS,
+                                            spdy::SpdyFrameType::RST_STREAM}));
+  EXPECT_FALSE(adapter->want_write());
+}
+
 TEST(OgHttp2AdapterServerTest, ServerSendsInvalidTrailers) {
   DataSavingVisitor visitor;
   OgHttp2Adapter::Options options{.perspective = Perspective::kServer};
diff --git a/http2/adapter/oghttp2_session.cc b/http2/adapter/oghttp2_session.cc
index c46f338..cf820b8 100644
--- a/http2/adapter/oghttp2_session.cc
+++ b/http2/adapter/oghttp2_session.cc
@@ -387,6 +387,7 @@
 }
 
 void OgHttp2Session::EnqueueFrame(std::unique_ptr<spdy::SpdyFrameIR> frame) {
+  RunOnExit r;
   if (frame->frame_type() == spdy::SpdyFrameType::GOAWAY) {
     queued_goaway_ = true;
   } else if (frame->fin() ||
@@ -397,6 +398,9 @@
     }
     if (frame->frame_type() == spdy::SpdyFrameType::RST_STREAM) {
       streams_reset_.insert(frame->stream_id());
+    } else if (iter != stream_map_.end()) {
+      // Enqueue RST_STREAM NO_ERROR if appropriate.
+      r = RunOnExit{[this, iter]() { MaybeFinWithRstStream(iter); }};
     }
   }
   if (frame->stream_id() != 0) {
@@ -579,6 +583,7 @@
                                    static_cast<int32_t>(max_frame_payload_)});
       if (fin) {
         state.half_closed_local = true;
+        MaybeFinWithRstStream(it);
       }
       AfterFrameSent(/* DATA */ 0, stream_id, length, fin ? 0x1 : 0x0, 0);
       if (!stream_map_.contains(stream_id)) {
@@ -1097,6 +1102,20 @@
   EnqueueFrame(std::move(frame));
 }
 
+void OgHttp2Session::MaybeFinWithRstStream(StreamStateMap::iterator iter) {
+  QUICHE_DCHECK(iter != stream_map_.end() && iter->second.half_closed_local);
+
+  if (options_.rst_stream_no_error_when_incomplete &&
+      options_.perspective == Perspective::kServer &&
+      !iter->second.half_closed_remote) {
+    // Since the peer has not yet ended the stream, this endpoint should
+    // send a RST_STREAM NO_ERROR. See RFC 7540 Section 8.1.
+    EnqueueFrame(absl::make_unique<spdy::SpdyRstStreamIR>(
+        iter->first, spdy::SpdyErrorCode::ERROR_CODE_NO_ERROR));
+    iter->second.half_closed_remote = true;
+  }
+}
+
 void OgHttp2Session::MarkDataBuffered(Http2StreamId stream_id, size_t bytes) {
   connection_window_manager_.MarkDataBuffered(bytes);
   auto it = stream_map_.find(stream_id);
diff --git a/http2/adapter/oghttp2_session.h b/http2/adapter/oghttp2_session.h
index b11b57c..c6a3e0f 100644
--- a/http2/adapter/oghttp2_session.h
+++ b/http2/adapter/oghttp2_session.h
@@ -36,6 +36,9 @@
     Perspective perspective = Perspective::kClient;
     // Whether to automatically send PING acks when receiving a PING.
     bool auto_ping_ack = true;
+    // Whether (as server) to send a RST_STREAM NO_ERROR when sending a fin on
+    // an incomplete stream.
+    bool rst_stream_no_error_when_incomplete = false;
   };
 
   OgHttp2Session(Http2VisitorInterface& visitor, Options options);
@@ -276,6 +279,10 @@
 
   void SendTrailers(Http2StreamId stream_id, spdy::SpdyHeaderBlock trailers);
 
+  // Encapsulates the RST_STREAM NO_ERROR behavior described in RFC 7540
+  // Section 8.1.
+  void MaybeFinWithRstStream(StreamStateMap::iterator iter);
+
   // Performs flow control accounting for data sent by the peer.
   void MarkDataBuffered(Http2StreamId stream_id, size_t bytes);