Send correct STOP_SENDING/RESET_STREAM frames for different stream types.

Upon receiving RESET_STREAM for a write-only stream, the connection will be closed with error.

gfe-relnote: protected by disabled v99 flag.
PiperOrigin-RevId: 279850700
Change-Id: I9f0e6215a388dac226d430458a340e8934432662
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 1888e03..1a8eef2 100644
--- a/quic/core/http/quic_spdy_server_stream_base_test.cc
+++ b/quic/core/http/quic_spdy_server_stream_base_test.cc
@@ -31,6 +31,7 @@
       : session_(new MockQuicConnection(&helper_,
                                         &alarm_factory_,
                                         Perspective::IS_SERVER)) {
+    session_.Initialize();
     stream_ =
         new TestQuicSpdyServerStream(GetNthClientInitiatedBidirectionalStreamId(
                                          session_.transport_version(), 0),
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index 205f7ad..3ec2401 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -9,6 +9,7 @@
 #include <utility>
 
 #include "net/third_party/quiche/src/quic/core/quic_connection.h"
+#include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
 #include "net/third_party/quiche/src/quic/core/quic_flow_controller.h"
 #include "net/third_party/quiche/src/quic/core/quic_types.h"
 #include "net/third_party/quiche/src/quic/core/quic_utils.h"
@@ -263,6 +264,8 @@
     return;
   }
 
+  stream->OnStopSending(frame.application_error_code);
+
   stream->set_stream_error(
       static_cast<QuicRstStreamErrorCode>(frame.application_error_code));
   SendRstStreamInner(
@@ -302,6 +305,16 @@
     return;
   }
 
+  if (VersionHasIetfQuicFrames(transport_version()) &&
+      QuicUtils::GetStreamType(stream_id, perspective(),
+                               IsIncomingStream(stream_id)) ==
+          WRITE_UNIDIRECTIONAL) {
+    connection()->CloseConnection(
+        QUIC_INVALID_STREAM_ID, "Received RESET_STREAM for a write-only stream",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return;
+  }
+
   if (visitor_) {
     visitor_->OnRstStreamReceived(frame);
   }
@@ -690,21 +703,23 @@
                                      bool close_write_side_only) {
   if (connection()->connected()) {
     // Only send if still connected.
-    if (close_write_side_only) {
-      DCHECK(VersionHasIetfQuicFrames(transport_version()));
-      // Send a RST_STREAM frame.
-      control_frame_manager_.WriteOrBufferRstStream(id, error, bytes_written);
-    } else {
+    if (VersionHasIetfQuicFrames(transport_version())) {
       // Send a RST_STREAM frame plus, if version 99, an IETF
       // QUIC STOP_SENDING frame. Both sre sent to emulate
       // the two-way close that Google QUIC's RST_STREAM does.
-      if (VersionHasIetfQuicFrames(transport_version())) {
-        QuicConnection::ScopedPacketFlusher flusher(connection());
-        control_frame_manager_.WriteOrBufferRstStream(id, error, bytes_written);
-        control_frame_manager_.WriteOrBufferStopSending(error, id);
-      } else {
+      QuicConnection::ScopedPacketFlusher flusher(connection());
+      if (QuicUtils::GetStreamType(id, perspective(), IsIncomingStream(id)) !=
+          READ_UNIDIRECTIONAL) {
         control_frame_manager_.WriteOrBufferRstStream(id, error, bytes_written);
       }
+      if (!close_write_side_only &&
+          QuicUtils::GetStreamType(id, perspective(), IsIncomingStream(id)) !=
+              WRITE_UNIDIRECTIONAL) {
+        control_frame_manager_.WriteOrBufferStopSending(error, id);
+      }
+    } else {
+      DCHECK(!close_write_side_only);
+      control_frame_manager_.WriteOrBufferRstStream(id, error, bytes_written);
     }
     connection_->OnStreamReset(id, error);
   }
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc
index d5d8321..4ee1230 100644
--- a/quic/core/quic_session_test.cc
+++ b/quic/core/quic_session_test.cc
@@ -389,24 +389,33 @@
   }
 
   void CloseStream(QuicStreamId id) {
-    if (VersionHasIetfQuicFrames(session_.transport_version()) &&
-        QuicUtils::GetStreamType(id, session_.perspective(),
-                                 session_.IsIncomingStream(id)) ==
-            READ_UNIDIRECTIONAL) {
-      // Verify reset is not sent for READ_UNIDIRECTIONAL streams.
-      EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
-      EXPECT_CALL(*connection_, OnStreamReset(_, _)).Times(0);
-    } else {
-      // Verify reset IS sent for BIDIRECTIONAL streams.
-      if (VersionHasIetfQuicFrames(session_.transport_version())) {
-        // Once for the RST_STREAM, Once for the STOP_SENDING
+    if (VersionHasIetfQuicFrames(transport_version())) {
+      if (QuicUtils::GetStreamType(id, session_.perspective(),
+                                   session_.IsIncomingStream(id)) ==
+          READ_UNIDIRECTIONAL) {
+        // Verify reset is not sent for READ_UNIDIRECTIONAL streams.
+        EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
+        EXPECT_CALL(*connection_, OnStreamReset(_, _)).Times(0);
+      } else if (QuicUtils::GetStreamType(id, session_.perspective(),
+                                          session_.IsIncomingStream(id)) ==
+                 WRITE_UNIDIRECTIONAL) {
+        // Verify RESET_STREAM but not STOP_SENDING is sent for write-only
+        // stream.
+        EXPECT_CALL(*connection_, SendControlFrame(_))
+            .Times(1)
+            .WillOnce(Invoke(&ClearControlFrame));
+        EXPECT_CALL(*connection_, OnStreamReset(id, _));
+      } else {
+        // Verify RESET_STREAM and STOP_SENDING are sent for BIDIRECTIONAL
+        // streams.
         EXPECT_CALL(*connection_, SendControlFrame(_))
             .Times(2)
             .WillRepeatedly(Invoke(&ClearControlFrame));
-      } else {
-        EXPECT_CALL(*connection_, SendControlFrame(_))
-            .WillOnce(Invoke(&ClearControlFrame));
+        EXPECT_CALL(*connection_, OnStreamReset(id, _));
       }
+    } else {
+      EXPECT_CALL(*connection_, SendControlFrame(_))
+          .WillOnce(Invoke(&ClearControlFrame));
       EXPECT_CALL(*connection_, OnStreamReset(id, _));
     }
     session_.CloseStream(id);
@@ -2753,6 +2762,41 @@
   session_.OnStreamFrame(frame1);
 }
 
+TEST_P(QuicSessionTestServer, ResetForIETFStreamTypes) {
+  if (!VersionHasIetfQuicFrames(transport_version())) {
+    return;
+  }
+
+  QuicStreamId read_only = GetNthClientInitiatedUnidirectionalId(0);
+  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
+  EXPECT_CALL(*connection_, OnStreamReset(read_only, _));
+  session_.SendRstStreamInner(read_only, QUIC_STREAM_CANCELLED, 0,
+                              /*close_write_side_only = */ true);
+
+  EXPECT_CALL(*connection_, SendControlFrame(_))
+      .Times(1)
+      .WillOnce(Invoke(&ClearControlFrame));
+  EXPECT_CALL(*connection_, OnStreamReset(read_only, _));
+  session_.SendRstStreamInner(read_only, QUIC_STREAM_CANCELLED, 0,
+                              /*close_write_side_only = */ false);
+
+  QuicStreamId write_only = GetNthServerInitiatedUnidirectionalId(0);
+  EXPECT_CALL(*connection_, SendControlFrame(_))
+      .Times(1)
+      .WillOnce(Invoke(&ClearControlFrame));
+  EXPECT_CALL(*connection_, OnStreamReset(write_only, _));
+  session_.SendRstStreamInner(write_only, QUIC_STREAM_CANCELLED, 0,
+                              /*close_write_side_only = */ false);
+
+  QuicStreamId bidirectional = GetNthClientInitiatedBidirectionalId(0);
+  EXPECT_CALL(*connection_, SendControlFrame(_))
+      .Times(2)
+      .WillRepeatedly(Invoke(&ClearControlFrame));
+  EXPECT_CALL(*connection_, OnStreamReset(bidirectional, _));
+  session_.SendRstStreamInner(bidirectional, QUIC_STREAM_CANCELLED, 0,
+                              /*close_write_side_only = */ false);
+}
+
 // A client test class that can be used when the automatic configuration is not
 // desired.
 class QuicSessionTestClientUnconfigured : public QuicSessionTestBase {
diff --git a/quic/core/quic_stream.h b/quic/core/quic_stream.h
index de0f619..c936bc7 100644
--- a/quic/core/quic_stream.h
+++ b/quic/core/quic_stream.h
@@ -336,6 +336,9 @@
   // this method or not.
   void SendStopSending(uint16_t code);
 
+  // Handle received StopSending frame.
+  virtual void OnStopSending(uint16_t /*code*/) {}
+
   // 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.
diff --git a/quic/tools/quic_simple_server_session_test.cc b/quic/tools/quic_simple_server_session_test.cc
index bb1cf19..4a12c09 100644
--- a/quic/tools/quic_simple_server_session_test.cc
+++ b/quic/tools/quic_simple_server_session_test.cc
@@ -15,6 +15,7 @@
 #include "net/third_party/quiche/src/quic/core/quic_connection.h"
 #include "net/third_party/quiche/src/quic/core/quic_crypto_server_stream.h"
 #include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_versions.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_containers.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
@@ -810,6 +811,12 @@
 // prevent a promised resource to be send out.
 TEST_P(QuicSimpleServerSessionServerPushTest,
        ResetPromisedStreamToCancelServerPush) {
+  if (VersionHasIetfQuicFrames(transport_version())) {
+    // This test is resetting a stream that is not opened yet. IETF QUIC has no
+    // way to handle this. Some similar tests can be added once CANCEL_PUSH is
+    // supported.
+    return;
+  }
   MaybeConsumeHeadersStreamData();
   session_->SetMaxAllowedPushId(kMaxQuicStreamId);
 
@@ -860,8 +867,6 @@
     EXPECT_CALL(*connection_,
                 SendStreamData(stream_not_reset, 1, offset, NO_FIN));
     offset++;
-  }
-  if (VersionUsesHttp3(connection_->transport_version())) {
     EXPECT_CALL(*connection_,
                 SendStreamData(stream_not_reset, kHeadersFrameHeaderLength,
                                offset, NO_FIN));
@@ -870,8 +875,6 @@
                 SendStreamData(stream_not_reset, kHeadersFramePayloadLength,
                                offset, NO_FIN));
     offset += kHeadersFramePayloadLength;
-  }
-  if (VersionUsesHttp3(connection_->transport_version())) {
     EXPECT_CALL(*connection_,
                 SendStreamData(stream_not_reset, data_frame_header_length,
                                offset, NO_FIN));
@@ -921,9 +924,9 @@
   // Resetting an open stream will close the stream and give space for extra
   // stream to be opened.
   QuicStreamId stream_got_reset = GetNthServerInitiatedUnidirectionalId(3);
-  EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1);
   EXPECT_CALL(*connection_, SendControlFrame(_));
   if (!VersionHasIetfQuicFrames(transport_version())) {
+    EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1);
     // For version 99, this is covered in InjectStopSending()
     EXPECT_CALL(*connection_,
                 OnStreamReset(stream_got_reset, QUIC_RST_ACKNOWLEDGEMENT));
@@ -933,8 +936,6 @@
     EXPECT_CALL(*connection_,
                 SendStreamData(stream_to_open, 1, offset, NO_FIN));
     offset++;
-  }
-  if (VersionUsesHttp3(connection_->transport_version())) {
     EXPECT_CALL(*connection_,
                 SendStreamData(stream_to_open, kHeadersFrameHeaderLength,
                                offset, NO_FIN));
@@ -943,8 +944,6 @@
                 SendStreamData(stream_to_open, kHeadersFramePayloadLength,
                                offset, NO_FIN));
     offset += kHeadersFramePayloadLength;
-  }
-  if (VersionUsesHttp3(connection_->transport_version())) {
     EXPECT_CALL(*connection_,
                 SendStreamData(stream_to_open, data_frame_header_length, offset,
                                NO_FIN));
@@ -965,8 +964,9 @@
     // available as it closes/etc them.
     session_->OnMaxStreamsFrame(
         QuicMaxStreamsFrame(0, num_resources + 3, /*unidirectional=*/true));
+  } else {
+    session_->OnRstStream(rst);
   }
-  session_->OnRstStream(rst);
   // Create and inject a STOP_SENDING frame. In GOOGLE QUIC, receiving a
   // RST_STREAM frame causes a two-way close. For IETF QUIC, RST_STREAM causes
   // a one-way close.