Introduce the concept of STOP_SENDING in gQUIC.

gQUIC uses to have RST(QUIC_STREAM_NO_ERROR) to achieve STOP_SENDING semantics. This change would unify this behavior with IETF STOP_SENDING.

Protected by quic_reloadable_flag_quic_split_up_send_rst.

PiperOrigin-RevId: 336959906
Change-Id: Ie1daa4a96bcdd627354b1425bd736a91f153beab
diff --git a/quic/core/http/quic_spdy_server_stream_base.cc b/quic/core/http/quic_spdy_server_stream_base.cc
index 2e2e2ec..aee5045 100644
--- a/quic/core/http/quic_spdy_server_stream_base.cc
+++ b/quic/core/http/quic_spdy_server_stream_base.cc
@@ -28,7 +28,11 @@
     DCHECK(fin_sent() || !session()->connection()->connected());
     // Tell the peer to stop sending further data.
     QUIC_DVLOG(1) << " Server: Send QUIC_STREAM_NO_ERROR on stream " << id();
-    Reset(QUIC_STREAM_NO_ERROR);
+    if (session()->split_up_send_rst()) {
+      MaybeSendStopSending(QUIC_STREAM_NO_ERROR);
+    } else {
+      Reset(QUIC_STREAM_NO_ERROR);
+    }
   }
 
   QuicSpdyStream::CloseWriteSide();
@@ -40,7 +44,11 @@
     DCHECK(fin_sent());
     // Tell the peer to stop sending further data.
     QUIC_DVLOG(1) << " Server: Send QUIC_STREAM_NO_ERROR on stream " << id();
-    Reset(QUIC_STREAM_NO_ERROR);
+    if (session()->split_up_send_rst()) {
+      MaybeSendStopSending(QUIC_STREAM_NO_ERROR);
+    } else {
+      Reset(QUIC_STREAM_NO_ERROR);
+    }
   }
   QuicSpdyStream::StopReading();
 }
diff --git a/quic/core/http/quic_spdy_server_stream_base_test.cc b/quic/core/http/quic_spdy_server_stream_base_test.cc
index 1d08da3..64184a7 100644
--- a/quic/core/http/quic_spdy_server_stream_base_test.cc
+++ b/quic/core/http/quic_spdy_server_stream_base_test.cc
@@ -50,7 +50,19 @@
 TEST_F(QuicSpdyServerStreamBaseTest,
        SendQuicRstStreamNoErrorWithEarlyResponse) {
   stream_->StopReading();
-  EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_NO_ERROR, _, _)).Times(1);
+
+  if (!session_.split_up_send_rst()) {
+    EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_NO_ERROR, _, _))
+        .Times(1);
+  } else {
+    if (session_.version().UsesHttp3()) {
+      EXPECT_CALL(session_, MaybeSendStopSendingFrame(_, QUIC_STREAM_NO_ERROR))
+          .Times(1);
+    } else {
+      EXPECT_CALL(session_, MaybeSendRstStreamFrame(_, QUIC_STREAM_NO_ERROR, _))
+          .Times(1);
+    }
+  }
   QuicStreamPeer::SetFinSent(stream_);
   stream_->CloseWriteSide();
 }
@@ -61,14 +73,25 @@
 
   EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_NO_ERROR, _, _)).Times(0);
 
-  EXPECT_CALL(
-      session_,
-      SendRstStream(_,
+  if (!session_.split_up_send_rst()) {
+    EXPECT_CALL(
+        session_,
+        SendRstStream(_,
+                      VersionHasIetfQuicFrames(session_.transport_version())
+                          ? QUIC_STREAM_CANCELLED
+                          : QUIC_RST_ACKNOWLEDGEMENT,
+                      _, _))
+        .Times(1);
+  } else {
+    EXPECT_CALL(session_,
+                MaybeSendRstStreamFrame(
+                    _,
                     VersionHasIetfQuicFrames(session_.transport_version())
                         ? QUIC_STREAM_CANCELLED
                         : QUIC_RST_ACKNOWLEDGEMENT,
-                    _, _))
-      .Times(1);
+                    _))
+        .Times(1);
+  }
   QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream_->id(),
                                QUIC_STREAM_CANCELLED, 1234);
   stream_->OnStreamReset(rst_frame);
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc
index 4adbaab..03afd72 100644
--- a/quic/core/http/quic_spdy_stream_test.cc
+++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -514,8 +514,13 @@
     stream_->OnStreamHeadersPriority(
         spdy::SpdyStreamPrecedence(kV3HighestPriority));
 
-    EXPECT_CALL(*session_,
-                SendRstStream(stream_->id(), QUIC_HEADERS_TOO_LARGE, 0, _));
+    if (!session_->split_up_send_rst()) {
+      EXPECT_CALL(*session_,
+                  SendRstStream(stream_->id(), QUIC_HEADERS_TOO_LARGE, 0, _));
+    } else {
+      EXPECT_CALL(*session_, MaybeSendRstStreamFrame(
+                                 stream_->id(), QUIC_HEADERS_TOO_LARGE, 0));
+    }
     stream_->OnStreamHeaderList(false, 1 << 20, headers);
 
     EXPECT_THAT(stream_->stream_error(), IsStreamError(QUIC_HEADERS_TOO_LARGE));
@@ -530,8 +535,15 @@
 
   QuicStreamFrame frame(stream_->id(), false, 0, headers);
 
-  EXPECT_CALL(*session_,
-              SendRstStream(stream_->id(), QUIC_HEADERS_TOO_LARGE, 0, _));
+  if (!session_->split_up_send_rst()) {
+    EXPECT_CALL(*session_,
+                SendRstStream(stream_->id(), QUIC_HEADERS_TOO_LARGE, 0, _));
+  } else {
+    EXPECT_CALL(*session_, MaybeSendStopSendingFrame(stream_->id(),
+                                                     QUIC_HEADERS_TOO_LARGE));
+    EXPECT_CALL(*session_, MaybeSendRstStreamFrame(stream_->id(),
+                                                   QUIC_HEADERS_TOO_LARGE, 0));
+  }
 
   auto qpack_decoder_stream =
       QuicSpdySessionPeer::GetQpackDecoderSendStream(session_.get());
@@ -667,8 +679,11 @@
                               ConnectionCloseSource source) {
         session_->ReallyOnConnectionClosed(frame, source);
       }));
-  EXPECT_CALL(*session_, SendRstStream(_, _, _, _));
-  EXPECT_CALL(*session_, SendRstStream(_, _, _, _));
+  if (!session_->split_up_send_rst()) {
+    EXPECT_CALL(*session_, SendRstStream(_, _, _, _)).Times(2);
+  } else {
+    EXPECT_CALL(*session_, MaybeSendRstStreamFrame(_, _, _)).Times(2);
+  }
 
   stream_->OnStreamFrame(frame);
 }
@@ -2097,8 +2112,11 @@
                               ConnectionCloseSource source) {
         session_->ReallyOnConnectionClosed(frame, source);
       }));
-  EXPECT_CALL(*session_, SendRstStream(_, _, _, _));
-  EXPECT_CALL(*session_, SendRstStream(_, _, _, _));
+  if (!session_->split_up_send_rst()) {
+    EXPECT_CALL(*session_, SendRstStream(_, _, _, _)).Times(2);
+  } else {
+    EXPECT_CALL(*session_, MaybeSendRstStreamFrame(_, _, _)).Times(2);
+  }
   stream_->OnStreamFrame(frame);
 }
 
@@ -2136,8 +2154,13 @@
           session_->ReallyOnConnectionClosed(frame, source);
         }));
   }
-  EXPECT_CALL(*session_, SendRstStream(stream_->id(), _, _, _));
-  EXPECT_CALL(*session_, SendRstStream(stream2_->id(), _, _, _));
+  if (!session_->split_up_send_rst()) {
+    EXPECT_CALL(*session_, SendRstStream(stream_->id(), _, _, _));
+    EXPECT_CALL(*session_, SendRstStream(stream2_->id(), _, _, _));
+  } else {
+    EXPECT_CALL(*session_, MaybeSendRstStreamFrame(stream_->id(), _, _));
+    EXPECT_CALL(*session_, MaybeSendRstStreamFrame(stream2_->id(), _, _));
+  }
 
   // Invalid headers: Required Insert Count is zero, but the header block
   // contains a dynamic table reference.
@@ -2456,8 +2479,15 @@
                          /* offset = */ 1, _, _, _));
 
   // Reset stream.
-  EXPECT_CALL(*session_,
-              SendRstStream(stream_->id(), QUIC_STREAM_CANCELLED, _, _));
+  if (!session_->split_up_send_rst()) {
+    EXPECT_CALL(*session_,
+                SendRstStream(stream_->id(), QUIC_STREAM_CANCELLED, _, _));
+  } else {
+    EXPECT_CALL(*session_, MaybeSendStopSendingFrame(stream_->id(),
+                                                     QUIC_STREAM_CANCELLED));
+    EXPECT_CALL(*session_, MaybeSendRstStreamFrame(stream_->id(),
+                                                   QUIC_STREAM_CANCELLED, _));
+  }
   stream_->Reset(QUIC_STREAM_CANCELLED);
 
   if (!GetQuicReloadableFlag(quic_abort_qpack_on_stream_close)) {
@@ -2917,8 +2947,15 @@
   EXPECT_CALL(*session_,
               WritevData(qpack_decoder_stream->id(), /* write_length = */ 1,
                          /* offset = */ 1, _, _, _));
-  EXPECT_CALL(*session_,
-              SendRstStream(stream_->id(), QUIC_STREAM_CANCELLED, 0, _));
+  if (!session_->split_up_send_rst()) {
+    EXPECT_CALL(*session_,
+                SendRstStream(stream_->id(), QUIC_STREAM_CANCELLED, _, _));
+  } else {
+    EXPECT_CALL(*session_, MaybeSendStopSendingFrame(stream_->id(),
+                                                     QUIC_STREAM_CANCELLED));
+    EXPECT_CALL(*session_, MaybeSendRstStreamFrame(stream_->id(),
+                                                   QUIC_STREAM_CANCELLED, _));
+  }
 
   stream_->Reset(QUIC_STREAM_CANCELLED);
 }
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index 34489e0..84f0a7f 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -804,15 +804,26 @@
                                 QuicRstStreamErrorCode error,
                                 QuicStreamOffset bytes_written,
                                 bool send_rst_only) {
-  if (connection()->connected()) {
-    QuicConnection::ScopedPacketFlusher flusher(connection());
-    MaybeSendRstStreamFrame(id, error, bytes_written);
-    if (!send_rst_only) {
-      MaybeSendStopSendingFrame(id, error);
-    }
-
-    connection_->OnStreamReset(id, error);
+  DCHECK(!split_up_send_rst());
+  if (!connection()->connected()) {
+    return;
   }
+
+  QuicConnection::ScopedPacketFlusher flusher(connection());
+  if (!VersionHasIetfQuicFrames(transport_version()) ||
+      QuicUtils::GetStreamType(id, perspective(), IsIncomingStream(id),
+                               version()) != READ_UNIDIRECTIONAL) {
+    control_frame_manager_.WriteOrBufferRstStream(id, error, bytes_written);
+  }
+  if (!send_rst_only) {
+    if (VersionHasIetfQuicFrames(transport_version()) &&
+        QuicUtils::GetStreamType(id, perspective(), IsIncomingStream(id),
+                                 version()) != WRITE_UNIDIRECTIONAL) {
+      control_frame_manager_.WriteOrBufferStopSending(error, id);
+    }
+  }
+
+  connection_->OnStreamReset(id, error);
 }
 
 void QuicSession::ResetStream(QuicStreamId id, QuicRstStreamErrorCode error) {
@@ -829,23 +840,37 @@
     return;
   }
 
-  SendRstStream(id, error, 0, /*send_rst_only = */ false);
+  if (split_up_send_rst()) {
+    QuicConnection::ScopedPacketFlusher flusher(connection());
+    MaybeSendStopSendingFrame(id, error);
+    MaybeSendRstStreamFrame(id, error, 0);
+  } else {
+    SendRstStream(id, error, 0, /*send_rst_only = */ false);
+  }
 }
 
 void QuicSession::MaybeSendRstStreamFrame(QuicStreamId id,
                                           QuicRstStreamErrorCode error,
                                           QuicStreamOffset bytes_written) {
-  DCHECK(connection()->connected());
+  DCHECK(split_up_send_rst());
+  if (!connection()->connected()) {
+    return;
+  }
   if (!VersionHasIetfQuicFrames(transport_version()) ||
       QuicUtils::GetStreamType(id, perspective(), IsIncomingStream(id),
                                version()) != READ_UNIDIRECTIONAL) {
     control_frame_manager_.WriteOrBufferRstStream(id, error, bytes_written);
   }
+
+  connection_->OnStreamReset(id, error);
 }
 
 void QuicSession::MaybeSendStopSendingFrame(QuicStreamId id,
                                             QuicRstStreamErrorCode error) {
-  DCHECK(connection()->connected());
+  DCHECK(split_up_send_rst());
+  if (!connection()->connected()) {
+    return;
+  }
   if (VersionHasIetfQuicFrames(transport_version()) &&
       QuicUtils::GetStreamType(id, perspective(), IsIncomingStream(id),
                                version()) != WRITE_UNIDIRECTIONAL) {
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h
index 8f26290..e84124e 100644
--- a/quic/core/quic_session.h
+++ b/quic/core/quic_session.h
@@ -34,6 +34,7 @@
 #include "net/third_party/quiche/src/quic/core/uber_quic_stream_id_manager.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_containers.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h"
 
 namespace quic {
@@ -216,6 +217,8 @@
 
   // Called by stream to send RST_STREAM (and STOP_SENDING in IETF QUIC).
   // if |send_rst_only|, STOP_SENDING will not be sent for IETF QUIC.
+  // TODO(b/170233449): Delete this method when flag quic_split_up_send_rst is
+  // deprecated.
   virtual void SendRstStream(QuicStreamId id,
                              QuicRstStreamErrorCode error,
                              QuicStreamOffset bytes_written,
@@ -332,6 +335,8 @@
     return connection_->connection_id();
   }
 
+  bool split_up_send_rst() const { return split_up_send_rst_; }
+
   // Returns the number of currently open streams, excluding static streams, and
   // never counting unfinished streams.
   size_t GetNumActiveStreams() const;
@@ -473,6 +478,16 @@
     return true;
   }
 
+  // Does actual work of sending RESET_STREAM, if the stream type allows.
+  // Also informs the connection so that pending stream frames can be flushed.
+  virtual void MaybeSendRstStreamFrame(QuicStreamId id,
+                                       QuicRstStreamErrorCode error,
+                                       QuicStreamOffset bytes_written);
+
+  // Sends a STOP_SENDING frame if the stream type allows.
+  virtual void MaybeSendStopSendingFrame(QuicStreamId id,
+                                         QuicRstStreamErrorCode error);
+
   const absl::optional<std::string> user_agent_id() const {
     return user_agent_id_;
   }
@@ -715,14 +730,6 @@
   // stream.
   void PendingStreamOnRstStream(const QuicRstStreamFrame& frame);
 
-  // Does actual work of sending RESET_STREAM, if the stream type allows.
-  void MaybeSendRstStreamFrame(QuicStreamId id,
-                               QuicRstStreamErrorCode error,
-                               QuicStreamOffset bytes_written);
-
-  // Sends a STOP_SENDING frame if the stream type allows.
-  void MaybeSendStopSendingFrame(QuicStreamId id, QuicRstStreamErrorCode error);
-
   // Keep track of highest received byte offset of locally closed streams, while
   // waiting for a definitive final highest offset from the peer.
   QuicHashMap<QuicStreamId, QuicStreamOffset>
@@ -835,6 +842,8 @@
   // This indicates a liveness testing is in progress, and push back the
   // creation of new outgoing bidirectional streams.
   bool liveness_testing_in_progress_;
+
+  const bool split_up_send_rst_ = GetQuicReloadableFlag(quic_split_up_send_rst);
 };
 
 }  // namespace quic
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc
index be2136b..5161977 100644
--- a/quic/core/quic_session_test.cc
+++ b/quic/core/quic_session_test.cc
@@ -3031,24 +3031,21 @@
       .Times(1)
       .WillOnce(Invoke(&ClearControlFrame));
   EXPECT_CALL(*connection_, OnStreamReset(read_only, _));
-  session_.SendRstStream(read_only, QUIC_STREAM_CANCELLED, 0,
-                         /*send_rst_only = */ false);
+  session_.ResetStream(read_only, QUIC_STREAM_CANCELLED);
 
   QuicStreamId write_only = GetNthServerInitiatedUnidirectionalId(0);
   EXPECT_CALL(*connection_, SendControlFrame(_))
       .Times(1)
       .WillOnce(Invoke(&ClearControlFrame));
   EXPECT_CALL(*connection_, OnStreamReset(write_only, _));
-  session_.SendRstStream(write_only, QUIC_STREAM_CANCELLED, 0,
-                         /*send_rst_only = */ false);
+  session_.ResetStream(write_only, QUIC_STREAM_CANCELLED);
 
   QuicStreamId bidirectional = GetNthClientInitiatedBidirectionalId(0);
   EXPECT_CALL(*connection_, SendControlFrame(_))
       .Times(2)
       .WillRepeatedly(Invoke(&ClearControlFrame));
   EXPECT_CALL(*connection_, OnStreamReset(bidirectional, _));
-  session_.SendRstStream(bidirectional, QUIC_STREAM_CANCELLED, 0,
-                         /*send_rst_only = */ false);
+  session_.ResetStream(bidirectional, QUIC_STREAM_CANCELLED);
 }
 
 TEST_P(QuicSessionTestServer, DecryptionKeyAvailableBeforeEncryptionKey) {
diff --git a/quic/core/quic_stream.cc b/quic/core/quic_stream.cc
index f84df2b..e9daac7 100644
--- a/quic/core/quic_stream.cc
+++ b/quic/core/quic_stream.cc
@@ -342,6 +342,7 @@
       fin_received_(fin_received),
       rst_sent_(false),
       rst_received_(false),
+      stop_sending_sent_(false),
       flow_controller_(std::move(flow_controller)),
       connection_flow_controller_(connection_flow_controller),
       stream_contributes_to_connection_flow_control_(true),
@@ -504,10 +505,15 @@
 
   stream_error_ = code;
 
-  session()->SendRstStream(id(), code, stream_bytes_written(),
-                           /*send_rst_only = */ true);
-  rst_sent_ = true;
-  CloseWriteSide();
+  if (session()->split_up_send_rst()) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_split_up_send_rst, 1, 3);
+    MaybeSendRstStream(code);
+  } else {
+    session()->SendRstStream(id(), code, stream_bytes_written(),
+                             /*send_rst_only = */ true);
+    rst_sent_ = true;
+    CloseWriteSide();
+  }
   return true;
 }
 
@@ -593,15 +599,23 @@
 
 void QuicStream::Reset(QuicRstStreamErrorCode error) {
   stream_error_ = error;
-  session()->SendRstStream(id(), error, stream_bytes_written(),
-                           /*send_rst_only = */ false);
-  rst_sent_ = true;
+  if (session()->split_up_send_rst()) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_split_up_send_rst, 2, 3);
+    MaybeSendStopSending(error);
+    MaybeSendRstStream(error);
+  } else {
+    session()->SendRstStream(id(), error, stream_bytes_written(),
+                             /*send_rst_only = */ false);
+    rst_sent_ = true;
+  }
   if (read_side_closed_ && write_side_closed_ && !IsWaitingForAcks()) {
     session()->MaybeCloseZombieStream(id_);
     return;
   }
-  CloseReadSide();
-  CloseWriteSide();
+  if (!session()->split_up_send_rst()) {
+    CloseReadSide();
+    CloseWriteSide();
+  }
 }
 
 void QuicStream::OnUnrecoverableError(QuicErrorCode error,
@@ -812,6 +826,44 @@
   }
 }
 
+void QuicStream::MaybeSendStopSending(QuicRstStreamErrorCode error) {
+  DCHECK(session()->split_up_send_rst());
+  if (stop_sending_sent_) {
+    return;
+  }
+
+  if (!session()->version().UsesHttp3() && error != QUIC_STREAM_NO_ERROR) {
+    // In gQUIC, RST with error closes both read and write side.
+    return;
+  }
+
+  if (session()->version().UsesHttp3()) {
+    session()->MaybeSendStopSendingFrame(id(), error);
+  } else {
+    DCHECK_EQ(QUIC_STREAM_NO_ERROR, error);
+    session()->MaybeSendRstStreamFrame(id(), QUIC_STREAM_NO_ERROR,
+                                       stream_bytes_written());
+  }
+  stop_sending_sent_ = true;
+  CloseReadSide();
+}
+
+void QuicStream::MaybeSendRstStream(QuicRstStreamErrorCode error) {
+  DCHECK(session()->split_up_send_rst());
+  if (rst_sent_) {
+    return;
+  }
+
+  if (!session()->version().UsesHttp3()) {
+    QUIC_BUG_IF(error == QUIC_STREAM_NO_ERROR);
+    stop_sending_sent_ = true;
+    CloseReadSide();
+  }
+  session()->MaybeSendRstStreamFrame(id(), error, stream_bytes_written());
+  rst_sent_ = true;
+  CloseWriteSide();
+}
+
 bool QuicStream::HasBufferedData() const {
   DCHECK_GE(send_buffer_.stream_offset(), stream_bytes_written());
   return send_buffer_.stream_offset() > stream_bytes_written();
@@ -834,14 +886,25 @@
   DCHECK(read_side_closed_ && write_side_closed_);
 
   if (!fin_sent_ && !rst_sent_) {
-    // For flow control accounting, tell the peer how many bytes have been
-    // written on this stream before termination. Done here if needed, using a
-    // RST_STREAM frame.
-    QUIC_DLOG(INFO) << ENDPOINT << "Sending RST_STREAM in OnClose: " << id();
-    session_->SendRstStream(id(), QUIC_RST_ACKNOWLEDGEMENT,
-                            stream_bytes_written(), /*send_rst_only = */ false);
-    session_->MaybeCloseZombieStream(id_);
-    rst_sent_ = true;
+    if (!session()->split_up_send_rst()) {
+      // For flow control accounting, tell the peer how many bytes have been
+      // written on this stream before termination. Done here if needed, using a
+      // RST_STREAM frame.
+      QUIC_DLOG(INFO) << ENDPOINT << "Sending RST_STREAM in OnClose: " << id();
+      session_->SendRstStream(id(), QUIC_RST_ACKNOWLEDGEMENT,
+                              stream_bytes_written(),
+                              /*send_rst_only = */ false);
+      session_->MaybeCloseZombieStream(id_);
+      rst_sent_ = true;
+    } else {
+      QUIC_RELOADABLE_FLAG_COUNT_N(quic_split_up_send_rst, 3, 3);
+      QUIC_BUG_IF(session()->connection()->connected() &&
+                  session()->version().UsesHttp3())
+          << "The stream should've already sent RST in response to "
+             "STOP_SENDING";
+      MaybeSendRstStream(QUIC_RST_ACKNOWLEDGEMENT);
+      session_->MaybeCloseZombieStream(id_);
+    }
   }
 
   if (!flow_controller_.has_value() ||
diff --git a/quic/core/quic_stream.h b/quic/core/quic_stream.h
index 89178c4..467cca9 100644
--- a/quic/core/quic_stream.h
+++ b/quic/core/quic_stream.h
@@ -403,6 +403,12 @@
   // empty.
   void SetFinSent();
 
+  // Send STOP_SENDING if it hasn't been sent yet.
+  void MaybeSendStopSending(QuicRstStreamErrorCode error);
+
+  // Send RESET_STREAM if it hasn't been sent yet.
+  void MaybeSendRstStream(QuicRstStreamErrorCode error);
+
   // Close the write side of the socket.  Further writes will fail.
   // Can be called by the subclass or internally.
   // Does not send a FIN.  May cause the stream to be closed.
@@ -509,6 +515,9 @@
   // True if this stream has received a RST_STREAM frame.
   bool rst_received_;
 
+  // True if the stream has sent STOP_SENDING to the session.
+  bool stop_sending_sent_;
+
   absl::optional<QuicFlowController> flow_controller_;
 
   // The connection level flow controller. Not owned.
diff --git a/quic/core/quic_stream_test.cc b/quic/core/quic_stream_test.cc
index 29bbaf5..fcd0d28 100644
--- a/quic/core/quic_stream_test.cc
+++ b/quic/core/quic_stream_test.cc
@@ -72,6 +72,7 @@
   using QuicStream::CanWriteNewDataAfterData;
   using QuicStream::CloseWriteSide;
   using QuicStream::fin_buffered;
+  using QuicStream::MaybeSendStopSending;
   using QuicStream::OnClose;
   using QuicStream::WriteMemSlices;
   using QuicStream::WriteOrBufferData;
@@ -112,8 +113,15 @@
     // session_ now owns stream_.
     session_->ActivateStream(QuicWrapUnique(stream_));
     // Ignore resetting when session_ is terminated.
-    EXPECT_CALL(*session_, SendRstStream(kTestStreamId, _, _, _))
-        .Times(AnyNumber());
+    if (!session_->split_up_send_rst()) {
+      EXPECT_CALL(*session_, SendRstStream(kTestStreamId, _, _, _))
+          .Times(AnyNumber());
+    } else {
+      EXPECT_CALL(*session_, MaybeSendStopSendingFrame(kTestStreamId, _))
+          .Times(AnyNumber());
+      EXPECT_CALL(*session_, MaybeSendRstStreamFrame(kTestStreamId, _, _))
+          .Times(AnyNumber());
+    }
     write_blocked_list_ =
         QuicSessionPeer::GetWriteBlockedStreams(session_.get());
   }
@@ -421,13 +429,21 @@
 TEST_P(QuicStreamTest, ConnectionCloseAfterStreamClose) {
   Initialize();
 
-  QuicStreamPeer::CloseReadSide(stream_);
-  stream_->CloseWriteSide();
-  EXPECT_THAT(stream_->stream_error(), IsQuicStreamNoError());
+  QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream_->id(),
+                               QUIC_STREAM_CANCELLED, 1234);
+  stream_->OnStreamReset(rst_frame);
+  if (VersionHasIetfQuicFrames(session_->transport_version())) {
+    // Create and inject a STOP SENDING frame to complete the close
+    // of the stream. This is only needed for version 99/IETF QUIC.
+    QuicStopSendingFrame stop_sending(kInvalidControlFrameId, stream_->id(),
+                                      QUIC_STREAM_CANCELLED);
+    session_->OnStopSendingFrame(stop_sending);
+  }
+  EXPECT_THAT(stream_->stream_error(), IsStreamError(QUIC_STREAM_CANCELLED));
   EXPECT_THAT(stream_->connection_error(), IsQuicNoError());
   stream_->OnConnectionClosed(QUIC_INTERNAL_ERROR,
                               ConnectionCloseSource::FROM_SELF);
-  EXPECT_THAT(stream_->stream_error(), IsQuicStreamNoError());
+  EXPECT_THAT(stream_->stream_error(), IsStreamError(QUIC_STREAM_CANCELLED));
   EXPECT_THAT(stream_->connection_error(), IsQuicNoError());
 }
 
@@ -452,9 +468,21 @@
   EXPECT_FALSE(rst_sent());
 
   // Now close the stream, and expect that we send a RST.
-  EXPECT_CALL(*session_, SendRstStream(_, _, _, _));
-  QuicStreamPeer::CloseReadSide(stream_);
-  stream_->CloseWriteSide();
+  if (!session_->split_up_send_rst()) {
+    EXPECT_CALL(*session_, SendRstStream(kTestStreamId, _, _, _));
+  } else {
+    EXPECT_CALL(*session_, MaybeSendRstStreamFrame(kTestStreamId, _, _));
+  }
+  QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream_->id(),
+                               QUIC_STREAM_CANCELLED, 1234);
+  stream_->OnStreamReset(rst_frame);
+  if (VersionHasIetfQuicFrames(session_->transport_version())) {
+    // Create and inject a STOP SENDING frame to complete the close
+    // of the stream. This is only needed for version 99/IETF QUIC.
+    QuicStopSendingFrame stop_sending(kInvalidControlFrameId, stream_->id(),
+                                      QUIC_STREAM_CANCELLED);
+    session_->OnStopSendingFrame(stop_sending);
+  }
   EXPECT_FALSE(session_->HasUnackedStreamData());
   EXPECT_FALSE(fin_sent());
   EXPECT_TRUE(rst_sent());
@@ -497,8 +525,12 @@
   EXPECT_FALSE(rst_sent());
 
   // Reset the stream.
-  const int expected_resets = 1;
-  EXPECT_CALL(*session_, SendRstStream(_, _, _, _)).Times(expected_resets);
+  if (!session_->split_up_send_rst()) {
+    EXPECT_CALL(*session_, SendRstStream(kTestStreamId, _, _, _)).Times(1);
+  } else {
+    EXPECT_CALL(*session_, MaybeSendRstStreamFrame(kTestStreamId, _, _))
+        .Times(1);
+  }
   stream_->Reset(QUIC_STREAM_CANCELLED);
   EXPECT_FALSE(fin_sent());
   EXPECT_TRUE(rst_sent());
@@ -639,8 +671,6 @@
               CloseConnection(QUIC_FLOW_CONTROL_RECEIVED_TOO_MUCH_DATA, _, _));
   stream_->OnStreamReset(rst_frame);
   EXPECT_TRUE(stream_->HasReceivedFinalOffset());
-  QuicStreamPeer::CloseReadSide(stream_);
-  stream_->CloseWriteSide();
 }
 
 TEST_P(QuicStreamTest, FinalByteOffsetFromZeroLengthStreamFrame) {
@@ -910,7 +940,11 @@
   EXPECT_TRUE(session_->HasUnackedStreamData());
   EXPECT_EQ(1u, QuicStreamPeer::SendBuffer(stream_).size());
   // Cancel stream.
-  stream_->Reset(QUIC_STREAM_NO_ERROR);
+  if (session_->split_up_send_rst()) {
+    stream_->MaybeSendStopSending(QUIC_STREAM_NO_ERROR);
+  } else {
+    stream_->Reset(QUIC_STREAM_NO_ERROR);
+  }
   // stream still waits for acks as the error code is QUIC_STREAM_NO_ERROR, and
   // data is going to be retransmitted.
   EXPECT_TRUE(stream_->IsWaitingForAcks());
@@ -920,12 +954,21 @@
   EXPECT_CALL(*connection_, SendControlFrame(_))
       .Times(AtLeast(1))
       .WillRepeatedly(Invoke(&ClearControlFrame));
-  EXPECT_CALL(*session_,
-              SendRstStream(stream_->id(), QUIC_STREAM_CANCELLED, 9, _))
-      .WillOnce(InvokeWithoutArgs([this]() {
-        session_->ReallySendRstStream(stream_->id(), QUIC_STREAM_CANCELLED,
-                                      stream_->stream_bytes_written(), false);
-      }));
+  if (!session_->split_up_send_rst()) {
+    EXPECT_CALL(*session_,
+                SendRstStream(stream_->id(), QUIC_STREAM_CANCELLED, 9, _))
+        .WillOnce(InvokeWithoutArgs([this]() {
+          session_->ReallySendRstStream(stream_->id(), QUIC_STREAM_CANCELLED,
+                                        stream_->stream_bytes_written(), false);
+        }));
+  } else {
+    EXPECT_CALL(*session_, MaybeSendRstStreamFrame(_, _, _))
+        .WillOnce(InvokeWithoutArgs([this]() {
+          session_->ReallyMaybeSendRstStreamFrame(
+              stream_->id(), QUIC_STREAM_CANCELLED,
+              stream_->stream_bytes_written());
+        }));
+  }
 
   stream_->Reset(QUIC_STREAM_CANCELLED);
   EXPECT_EQ(1u, QuicStreamPeer::SendBuffer(stream_).size());
@@ -956,8 +999,14 @@
   // RST_STREAM received.
   QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream_->id(),
                                QUIC_STREAM_CANCELLED, 9);
-  EXPECT_CALL(*session_,
-              SendRstStream(stream_->id(), QUIC_RST_ACKNOWLEDGEMENT, 9, _));
+
+  if (!session_->split_up_send_rst()) {
+    EXPECT_CALL(*session_,
+                SendRstStream(stream_->id(), QUIC_RST_ACKNOWLEDGEMENT, 9, _));
+  } else {
+    EXPECT_CALL(*session_, MaybeSendRstStreamFrame(
+                               stream_->id(), QUIC_RST_ACKNOWLEDGEMENT, 9));
+  }
   stream_->OnStreamReset(rst_frame);
   EXPECT_EQ(1u, QuicStreamPeer::SendBuffer(stream_).size());
   // Stream stops waiting for acks as it does not finish sending and rst is
@@ -1000,8 +1049,14 @@
   stream_->WriteOrBufferData(kData1, false, nullptr);
   EXPECT_TRUE(stream_->IsWaitingForAcks());
   EXPECT_TRUE(session_->HasUnackedStreamData());
-  EXPECT_CALL(*session_,
-              SendRstStream(stream_->id(), QUIC_RST_ACKNOWLEDGEMENT, 9, _));
+  if (!session_->split_up_send_rst()) {
+    EXPECT_CALL(*session_,
+                SendRstStream(stream_->id(), QUIC_RST_ACKNOWLEDGEMENT, 9, _));
+  } else {
+    EXPECT_CALL(*session_, MaybeSendRstStreamFrame(
+                               stream_->id(), QUIC_RST_ACKNOWLEDGEMENT, 9));
+  }
+  QuicConnectionPeer::SetConnectionClose(connection_);
   stream_->OnConnectionClosed(QUIC_INTERNAL_ERROR,
                               ConnectionCloseSource::FROM_SELF);
   EXPECT_EQ(1u, QuicStreamPeer::SendBuffer(stream_).size());
@@ -1525,8 +1580,19 @@
 
   connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
   // Verify stream gets reset because TTL expires.
-  EXPECT_CALL(*session_, SendRstStream(_, QUIC_STREAM_TTL_EXPIRED, _, _))
-      .Times(1);
+  if (!session_->split_up_send_rst()) {
+    EXPECT_CALL(*session_, SendRstStream(_, QUIC_STREAM_TTL_EXPIRED, _, _))
+        .Times(1);
+  } else {
+    if (session_->version().UsesHttp3()) {
+      EXPECT_CALL(*session_,
+                  MaybeSendStopSendingFrame(_, QUIC_STREAM_TTL_EXPIRED))
+          .Times(1);
+    }
+    EXPECT_CALL(*session_,
+                MaybeSendRstStreamFrame(_, QUIC_STREAM_TTL_EXPIRED, _))
+        .Times(1);
+  }
   stream_->OnCanWrite();
 }
 
@@ -1544,8 +1610,19 @@
 
   connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
   // Verify stream gets reset because TTL expires.
-  EXPECT_CALL(*session_, SendRstStream(_, QUIC_STREAM_TTL_EXPIRED, _, _))
-      .Times(1);
+  if (!session_->split_up_send_rst()) {
+    EXPECT_CALL(*session_, SendRstStream(_, QUIC_STREAM_TTL_EXPIRED, _, _))
+        .Times(1);
+  } else {
+    if (session_->version().UsesHttp3()) {
+      EXPECT_CALL(*session_,
+                  MaybeSendStopSendingFrame(_, QUIC_STREAM_TTL_EXPIRED))
+          .Times(1);
+    }
+    EXPECT_CALL(*session_,
+                MaybeSendRstStreamFrame(_, QUIC_STREAM_TTL_EXPIRED, _))
+        .Times(1);
+  }
   stream_->RetransmitStreamData(0, 100, false, PTO_RETRANSMISSION);
 }
 
diff --git a/quic/qbone/qbone_stream_test.cc b/quic/qbone/qbone_stream_test.cc
index c2b28ca..17cd2e1 100644
--- a/quic/qbone/qbone_stream_test.cc
+++ b/quic/qbone/qbone_stream_test.cc
@@ -65,6 +65,16 @@
               SendRstStream,
               (QuicStreamId, QuicRstStreamErrorCode, QuicStreamOffset, bool),
               (override));
+  MOCK_METHOD(void,
+              MaybeSendRstStreamFrame,
+              (QuicStreamId stream_id,
+               QuicRstStreamErrorCode error,
+               QuicStreamOffset bytes_written),
+              (override));
+  MOCK_METHOD(void,
+              MaybeSendStopSendingFrame,
+              (QuicStreamId stream_id, QuicRstStreamErrorCode error),
+              (override));
 
   // Sets whether data is written to buffer, or else if this is write blocked.
   void set_writable(bool writable) { writable_ = writable; }
@@ -237,8 +247,15 @@
   CreateReliableQuicStream();
   std::string packet = "0123456789";
   int iterations = (QboneConstants::kMaxQbonePacketBytes / packet.size()) + 2;
-  EXPECT_CALL(*session_,
-              SendRstStream(kStreamId, QUIC_BAD_APPLICATION_PAYLOAD, _, _));
+  if (!session_->split_up_send_rst()) {
+    EXPECT_CALL(*session_,
+                SendRstStream(kStreamId, QUIC_BAD_APPLICATION_PAYLOAD, _, _));
+  } else {
+    EXPECT_CALL(*session_, MaybeSendStopSendingFrame(
+                               kStreamId, QUIC_BAD_APPLICATION_PAYLOAD));
+    EXPECT_CALL(*session_, MaybeSendRstStreamFrame(
+                               kStreamId, QUIC_BAD_APPLICATION_PAYLOAD, _));
+  }
   for (int i = 0; i < iterations; ++i) {
     QuicStreamFrame frame(kStreamId, i == (iterations - 1), i * packet.size(),
                           packet);
diff --git a/quic/test_tools/quic_connection_peer.cc b/quic/test_tools/quic_connection_peer.cc
index 1f4e0c8..6b092c8 100644
--- a/quic/test_tools/quic_connection_peer.cc
+++ b/quic/test_tools/quic_connection_peer.cc
@@ -384,5 +384,9 @@
   return connection->pending_path_challenge_payloads_;
 }
 
+void QuicConnectionPeer::SetConnectionClose(QuicConnection* connection) {
+  connection->connected_ = false;
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/quic/test_tools/quic_connection_peer.h b/quic/test_tools/quic_connection_peer.h
index 783b055..bf404fb 100644
--- a/quic/test_tools/quic_connection_peer.h
+++ b/quic/test_tools/quic_connection_peer.h
@@ -159,6 +159,8 @@
   static const QuicCircularDeque<
       std::pair<QuicPathFrameBuffer, QuicSocketAddress>>&
   pending_path_challenge_payloads(QuicConnection* connection);
+
+  static void SetConnectionClose(QuicConnection* connection);
 };
 
 }  // namespace test
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h
index b0052d8..f2ad3be 100644
--- a/quic/test_tools/quic_test_utils.h
+++ b/quic/test_tools/quic_test_utils.h
@@ -836,6 +836,16 @@
                QuicStreamOffset bytes_written,
                bool send_rst_only),
               (override));
+  MOCK_METHOD(void,
+              MaybeSendRstStreamFrame,
+              (QuicStreamId stream_id,
+               QuicRstStreamErrorCode error,
+               QuicStreamOffset bytes_written),
+              (override));
+  MOCK_METHOD(void,
+              MaybeSendStopSendingFrame,
+              (QuicStreamId stream_id, QuicRstStreamErrorCode error),
+              (override));
 
   MOCK_METHOD(bool, ShouldKeepConnectionAlive, (), (const, override));
   MOCK_METHOD(void,
@@ -867,6 +877,12 @@
     QuicSession::SendRstStream(id, error, bytes_written, send_rst_only);
   }
 
+  void ReallyMaybeSendRstStreamFrame(QuicStreamId id,
+                                     QuicRstStreamErrorCode error,
+                                     QuicStreamOffset bytes_written) {
+    QuicSession::MaybeSendRstStreamFrame(id, error, bytes_written);
+  }
+
  private:
   std::unique_ptr<QuicCryptoStream> crypto_stream_;
 };
@@ -968,6 +984,16 @@
                bool send_rst_only),
               (override));
   MOCK_METHOD(void,
+              MaybeSendRstStreamFrame,
+              (QuicStreamId stream_id,
+               QuicRstStreamErrorCode error,
+               QuicStreamOffset bytes_written),
+              (override));
+  MOCK_METHOD(void,
+              MaybeSendStopSendingFrame,
+              (QuicStreamId stream_id, QuicRstStreamErrorCode error),
+              (override));
+  MOCK_METHOD(void,
               SendWindowUpdate,
               (QuicStreamId id, QuicStreamOffset byte_offset),
               (override));
diff --git a/quic/tools/quic_simple_server_stream_test.cc b/quic/tools/quic_simple_server_stream_test.cc
index c5d989a..251f02d 100644
--- a/quic/tools/quic_simple_server_stream_test.cc
+++ b/quic/tools/quic_simple_server_stream_test.cc
@@ -12,6 +12,7 @@
 #include "absl/types/optional.h"
 #include "net/third_party/quiche/src/quic/core/http/http_encoder.h"
 #include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
 #include "net/third_party/quiche/src/quic/core/quic_types.h"
 #include "net/third_party/quiche/src/quic/core/quic_utils.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
@@ -158,6 +159,16 @@
                QuicStreamOffset bytes_written,
                bool send_rst_only),
               (override));
+  MOCK_METHOD(void,
+              MaybeSendRstStreamFrame,
+              (QuicStreamId stream_id,
+               QuicRstStreamErrorCode error,
+               QuicStreamOffset bytes_written),
+              (override));
+  MOCK_METHOD(void,
+              MaybeSendStopSendingFrame,
+              (QuicStreamId stream_id, QuicRstStreamErrorCode error),
+              (override));
   // Matchers cannot be used on non-copyable types like SpdyHeaderBlock.
   void PromisePushResources(
       const std::string& request_url,
@@ -341,7 +352,18 @@
   QuicStreamPeer::SetFinSent(stream_);
   stream_->CloseWriteSide();
 
-  EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_NO_ERROR, _, _)).Times(1);
+  if (!session_.split_up_send_rst()) {
+    EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_NO_ERROR, _, _))
+        .Times(1);
+  } else {
+    if (session_.version().UsesHttp3()) {
+      EXPECT_CALL(session_, MaybeSendStopSendingFrame(_, QUIC_STREAM_NO_ERROR))
+          .Times(1);
+    } else {
+      EXPECT_CALL(session_, MaybeSendRstStreamFrame(_, QUIC_STREAM_NO_ERROR, _))
+          .Times(1);
+    }
+  }
   stream_->StopReading();
 }
 
@@ -469,8 +491,16 @@
                                     std::move(response_headers_), body);
 
   InSequence s;
-  EXPECT_CALL(session_, SendRstStream(promised_stream->id(),
-                                      QUIC_STREAM_CANCELLED, 0, _));
+  if (!session_.split_up_send_rst()) {
+    EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_CANCELLED, 0, _));
+  } else {
+    if (session_.version().UsesHttp3()) {
+      EXPECT_CALL(session_, MaybeSendStopSendingFrame(promised_stream->id(),
+                                                      QUIC_STREAM_CANCELLED));
+    }
+    EXPECT_CALL(session_, MaybeSendRstStreamFrame(promised_stream->id(),
+                                                  QUIC_STREAM_CANCELLED, 0));
+  }
 
   promised_stream->DoSendResponse();
 }
@@ -674,7 +704,6 @@
 
 TEST_P(QuicSimpleServerStreamTest,
        DoNotSendQuicRstStreamNoErrorWithRstReceived) {
-  InSequence s;
   EXPECT_FALSE(stream_->reading_stopped());
 
   EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_NO_ERROR, _, _)).Times(0);
@@ -687,16 +716,33 @@
     EXPECT_CALL(session_, WritevData(qpack_decoder_stream->id(), _, _, _, _, _))
         .Times(AnyNumber());
   }
-  EXPECT_CALL(session_, SendRstStream(_, QUIC_RST_ACKNOWLEDGEMENT, _, _))
-      .Times(1);
+
+  if (!session_.split_up_send_rst()) {
+    EXPECT_CALL(session_, SendRstStream(_,
+                                        session_.version().UsesHttp3()
+                                            ? QUIC_STREAM_CANCELLED
+                                            : QUIC_RST_ACKNOWLEDGEMENT,
+                                        _, _))
+        .Times(1);
+  } else {
+    EXPECT_CALL(session_,
+                MaybeSendRstStreamFrame(_,
+                                        session_.version().UsesHttp3()
+                                            ? QUIC_STREAM_CANCELLED
+                                            : QUIC_RST_ACKNOWLEDGEMENT,
+                                        _))
+        .Times(1);
+  }
   QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream_->id(),
                                QUIC_STREAM_CANCELLED, 1234);
   stream_->OnStreamReset(rst_frame);
   if (VersionHasIetfQuicFrames(connection_->transport_version())) {
-    // For V99 receiving a RST_STREAM causes a 1-way close; the test requires
-    // a full close. A CloseWriteSide closes the other half of the stream.
-    // Everything should then work properly.
-    stream_->CloseWriteSide();
+    EXPECT_CALL(session_owner_, OnStopSendingReceived(_));
+    // Create and inject a STOP SENDING frame to complete the close
+    // of the stream. This is only needed for version 99/IETF QUIC.
+    QuicStopSendingFrame stop_sending(kInvalidControlFrameId, stream_->id(),
+                                      QUIC_STREAM_CANCELLED);
+    session_.OnStopSendingFrame(stop_sending);
   }
   EXPECT_TRUE(stream_->reading_stopped());
   EXPECT_TRUE(stream_->write_side_closed());