Allow STOP_SENDING frame to be the first frame of a stream.

gfe-relnote: protected by gfe2_reloadable_flag_quic_enable_version_draft_25_v3
PiperOrigin-RevId: 299930073
Change-Id: I2ed39bf568f75498e8c2ab6d8c82f99466e8b9ed
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index 0ca240e..e913ee1 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -248,38 +248,9 @@
     visitor_->OnStopSendingReceived(frame);
   }
 
-  // If stream is closed, ignore the frame
-  if (IsClosedStream(stream_id)) {
-    QUIC_DVLOG(1)
-        << ENDPOINT
-        << "Received STOP_SENDING for closed or non-existent stream, id: "
-        << stream_id << " Ignoring.";
-    return;
-  }
-  // If stream is non-existent, close the connection.
-  // TODO(b/148842616): IETF QUIC allows a STOP_SENDING to arrive before a
-  // STREAM frame for peer-intiated bidirectional steams
-  StreamMap::iterator it = stream_map_.find(stream_id);
-  if (it == stream_map_.end()) {
-    QUIC_DVLOG(1) << ENDPOINT
-                  << "Received STOP_SENDING for non-existent stream, id: "
-                  << stream_id << " Closing connection";
-    connection()->CloseConnection(
-        IETF_QUIC_PROTOCOL_VIOLATION,
-        "Received STOP_SENDING for a non-existent stream",
-        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
-    return;
-  }
-
-  QuicStream* stream = it->second.get();
-  // If the stream is null, it's an implementation error.
-  if (stream == nullptr) {
-    QUIC_BUG << ENDPOINT
-             << "Received STOP_SENDING for NULL QuicStream, stream_id: "
-             << stream_id << ". Ignoring.";
-    connection()->CloseConnection(
-        QUIC_INTERNAL_ERROR, "Received STOP_SENDING for a null stream",
-        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+  QuicStream* stream = GetOrCreateStream(stream_id);
+  if (!stream) {
+    // Errors are handled by GetOrCreateStream.
     return;
   }
 
@@ -287,7 +258,7 @@
     return;
   }
 
-  // TODO(renjietang): Consider moving those code into the
+  // TODO(renjietang): Consider moving those code into the stream.
   if (connection()->connected()) {
     MaybeSendRstStreamFrame(
         stream->id(),
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc
index 4f8dd4f..0d7d80e 100644
--- a/quic/core/quic_session_test.cc
+++ b/quic/core/quic_session_test.cc
@@ -2695,22 +2695,40 @@
   session_.OnStopSendingFrame(frame);
 }
 
-// If stream id is a nonexistent stream, return false and close the connection.
-TEST_P(QuicSessionTestServer, OnStopSendingInputNonExistentStream) {
+// If stream id is a nonexistent local stream, return false and close the
+// connection.
+TEST_P(QuicSessionTestServer, OnStopSendingInputNonExistentLocalStream) {
   if (!VersionHasIetfQuicFrames(transport_version())) {
     return;
   }
 
   QuicStopSendingFrame frame(1, GetNthServerInitiatedBidirectionalId(123456),
                              123);
-  EXPECT_CALL(
-      *connection_,
-      CloseConnection(IETF_QUIC_PROTOCOL_VIOLATION,
-                      "Received STOP_SENDING for a non-existent stream", _))
+  EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID,
+                                            "Data for nonexistent stream", _))
       .Times(1);
   session_.OnStopSendingFrame(frame);
 }
 
+// If a STOP_SENDING is received for a peer initiated stream, the new stream
+// will be created.
+TEST_P(QuicSessionTestServer, OnStopSendingNewStream) {
+  if (!VersionHasIetfQuicFrames(transport_version())) {
+    return;
+  }
+  QuicStopSendingFrame frame(1, GetNthClientInitiatedBidirectionalId(1), 123);
+
+  // A Rst will be sent as a response for STOP_SENDING.
+  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(1);
+  EXPECT_CALL(*connection_, OnStreamReset(_, _)).Times(1);
+  session_.OnStopSendingFrame(frame);
+
+  QuicStream* stream =
+      session_.GetOrCreateStream(GetNthClientInitiatedBidirectionalId(1));
+  EXPECT_TRUE(stream);
+  EXPECT_TRUE(stream->write_side_closed());
+}
+
 // For a valid stream, ensure that all works
 TEST_P(QuicSessionTestServer, OnStopSendingInputValidStream) {
   if (!VersionHasIetfQuicFrames(transport_version())) {