Close connection when stream receives wrong data regarding close offset.

gfe-relnote: protected by gfe2_reloadable_flag_quic_close_connection_on_wrong_offset.
PiperOrigin-RevId: 276352453
Change-Id: Ia4174e54d01a2d8c2aa10182940c1093c1a16ad8
diff --git a/quic/core/quic_error_codes.cc b/quic/core/quic_error_codes.cc
index a603bc1..ae17f09 100644
--- a/quic/core/quic_error_codes.cc
+++ b/quic/core/quic_error_codes.cc
@@ -163,6 +163,8 @@
     RETURN_STRING_LITERAL(QUIC_QPACK_DECOMPRESSION_FAILED);
     RETURN_STRING_LITERAL(QUIC_QPACK_ENCODER_STREAM_ERROR);
     RETURN_STRING_LITERAL(QUIC_QPACK_DECODER_STREAM_ERROR);
+    RETURN_STRING_LITERAL(QUIC_STREAM_DATA_BEYOND_CLOSE_OFFSET);
+    RETURN_STRING_LITERAL(QUIC_STREAM_MULTIPLE_OFFSET);
 
     RETURN_STRING_LITERAL(QUIC_LAST_ERROR);
     // Intentionally have no default case, so we'll break the build
diff --git a/quic/core/quic_error_codes.h b/quic/core/quic_error_codes.h
index 6008ab6..b6c2d50 100644
--- a/quic/core/quic_error_codes.h
+++ b/quic/core/quic_error_codes.h
@@ -347,8 +347,14 @@
   QUIC_QPACK_ENCODER_STREAM_ERROR = 127,
   QUIC_QPACK_DECODER_STREAM_ERROR = 128,
 
+  // Received stream data beyond close offset.
+  QUIC_STREAM_DATA_BEYOND_CLOSE_OFFSET = 129,
+
+  // Received multiple close offset.
+  QUIC_STREAM_MULTIPLE_OFFSET = 130,
+
   // No error. Used as bound while iterating.
-  QUIC_LAST_ERROR = 129,
+  QUIC_LAST_ERROR = 131,
 };
 // QuicErrorCodes is encoded as four octets on-the-wire when doing Google QUIC,
 // or a varint62 when doing IETF QUIC. Ensure that its value does not exceed
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc
index d128f0d..845277b 100644
--- a/quic/core/quic_session_test.cc
+++ b/quic/core/quic_session_test.cc
@@ -2746,11 +2746,15 @@
   session_.OnStreamFrame(frame);
 
   QuicStreamFrame frame1(stream->id(), false, 1, ",");
-  EXPECT_CALL(*connection_, SendControlFrame(_));
-  EXPECT_CALL(*connection_,
-              OnStreamReset(stream->id(), QUIC_DATA_AFTER_CLOSE_OFFSET));
+  if (!GetQuicReloadableFlag(quic_close_connection_on_wrong_offset)) {
+    EXPECT_CALL(*connection_, SendControlFrame(_));
+    EXPECT_CALL(*connection_,
+                OnStreamReset(stream->id(), QUIC_DATA_AFTER_CLOSE_OFFSET));
+  } else {
+    EXPECT_CALL(*connection_,
+                CloseConnection(QUIC_STREAM_DATA_BEYOND_CLOSE_OFFSET, _, _));
+  }
   session_.OnStreamFrame(frame1);
-  EXPECT_TRUE(connection_->connected());
 }
 
 // A client test class that can be used when the automatic configuration is not
diff --git a/quic/core/quic_stream.cc b/quic/core/quic_stream.cc
index 4b70bd4..aced2b9 100644
--- a/quic/core/quic_stream.cc
+++ b/quic/core/quic_stream.cc
@@ -9,6 +9,7 @@
 #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_session.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_bug_tracker.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h"
@@ -172,7 +173,12 @@
   }
 
   if (frame.offset + frame.data_length > sequencer_.close_offset()) {
-    Reset(QUIC_DATA_AFTER_CLOSE_OFFSET);
+    CloseConnectionWithDetails(
+        QUIC_STREAM_DATA_BEYOND_CLOSE_OFFSET,
+        QuicStrCat(
+            "Stream ", id_,
+            " received data with offset: ", frame.offset + frame.data_length,
+            ", which is beyond close offset: ", sequencer()->close_offset()));
     return;
   }
 
@@ -211,6 +217,20 @@
                                "Reset frame stream offset overflow.");
     return;
   }
+
+  const QuicStreamOffset kMaxOffset =
+      std::numeric_limits<QuicStreamOffset>::max();
+  if (sequencer()->close_offset() != kMaxOffset &&
+      frame.byte_offset != sequencer()->close_offset()) {
+    CloseConnectionWithDetails(
+        QUIC_STREAM_MULTIPLE_OFFSET,
+        QuicStrCat("Stream ", id_,
+                   " received new final offset: ", frame.byte_offset,
+                   ", which is different from close offset: ",
+                   sequencer()->close_offset()));
+    return;
+  }
+
   MaybeIncreaseHighestReceivedOffset(frame.byte_offset);
   if (flow_controller_.FlowControlViolation() ||
       connection_flow_controller_->FlowControlViolation()) {
@@ -402,7 +422,17 @@
   }
 
   if (frame.offset + frame.data_length > sequencer_.close_offset()) {
-    Reset(QUIC_DATA_AFTER_CLOSE_OFFSET);
+    if (!GetQuicReloadableFlag(quic_close_connection_on_wrong_offset)) {
+      Reset(QUIC_DATA_AFTER_CLOSE_OFFSET);
+      return;
+    }
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_close_connection_on_wrong_offset, 1, 2);
+    CloseConnectionWithDetails(
+        QUIC_STREAM_DATA_BEYOND_CLOSE_OFFSET,
+        QuicStrCat(
+            "Stream ", id_,
+            " received data with offset: ", frame.offset + frame.data_length,
+            ", which is beyond close offset: ", sequencer_.close_offset()));
     return;
   }
 
@@ -459,6 +489,23 @@
                                "Reset frame stream offset overflow.");
     return;
   }
+
+  if (GetQuicReloadableFlag(quic_close_connection_on_wrong_offset)) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_close_connection_on_wrong_offset, 2, 2);
+    const QuicStreamOffset kMaxOffset =
+        std::numeric_limits<QuicStreamOffset>::max();
+    if (sequencer()->close_offset() != kMaxOffset &&
+        frame.byte_offset != sequencer()->close_offset()) {
+      CloseConnectionWithDetails(
+          QUIC_STREAM_MULTIPLE_OFFSET,
+          QuicStrCat("Stream ", id_,
+                     " received new final offset: ", frame.byte_offset,
+                     ", which is different from close offset: ",
+                     sequencer_.close_offset()));
+      return;
+    }
+  }
+
   MaybeIncreaseHighestReceivedOffset(frame.byte_offset);
   if (flow_controller_->FlowControlViolation() ||
       connection_flow_controller_->FlowControlViolation()) {
diff --git a/quic/core/quic_stream_test.cc b/quic/core/quic_stream_test.cc
index eaeb630..68be6be 100644
--- a/quic/core/quic_stream_test.cc
+++ b/quic/core/quic_stream_test.cc
@@ -8,8 +8,10 @@
 #include <string>
 #include <utility>
 
+#include "net/third_party/quiche/src/quic/core/frames/quic_rst_stream_frame.h"
 #include "net/third_party/quiche/src/quic/core/quic_connection.h"
 #include "net/third_party/quiche/src/quic/core/quic_constants.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/core/quic_versions.h"
@@ -1631,6 +1633,19 @@
   stream.OnWindowUpdateFrame(window_update_frame);
 }
 
+TEST_P(QuicStreamTest, RstStreamFrameChangesCloseOffset) {
+  SetQuicReloadableFlag(quic_close_connection_on_wrong_offset, true);
+  Initialize();
+
+  QuicStreamFrame stream_frame(stream_->id(), true, 0, "abc");
+  stream_->OnStreamFrame(stream_frame);
+  QuicRstStreamFrame rst(kInvalidControlFrameId, stream_->id(),
+                         QUIC_STREAM_CANCELLED, 0u);
+
+  EXPECT_CALL(*connection_, CloseConnection(QUIC_STREAM_MULTIPLE_OFFSET, _, _));
+  stream_->OnStreamReset(rst);
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/quic_types.cc b/quic/core/quic_types.cc
index e6f95cd..c584714 100644
--- a/quic/core/quic_types.cc
+++ b/quic/core/quic_types.cc
@@ -6,6 +6,7 @@
 
 #include <cstdint>
 
+#include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.h"
 
 namespace quic {
@@ -425,6 +426,11 @@
       return {
           false,
           {static_cast<uint64_t>(IETF_QUIC_HTTP_QPACK_DECODER_STREAM_ERROR)}};
+    case QUIC_STREAM_DATA_BEYOND_CLOSE_OFFSET:
+      return {true,
+              {static_cast<uint64_t>(QUIC_STREAM_DATA_BEYOND_CLOSE_OFFSET)}};
+    case QUIC_STREAM_MULTIPLE_OFFSET:
+      return {true, {static_cast<uint64_t>(QUIC_STREAM_MULTIPLE_OFFSET)}};
     case QUIC_LAST_ERROR:
       return {false, {static_cast<uint64_t>(QUIC_LAST_ERROR)}};
   }
diff --git a/quic/test_tools/quic_test_client.cc b/quic/test_tools/quic_test_client.cc
index 237d9f6..ca5e95a 100644
--- a/quic/test_tools/quic_test_client.cc
+++ b/quic/test_tools/quic_test_client.cc
@@ -382,7 +382,9 @@
 
   QuicStreamId stream_id = GetNthClientInitiatedBidirectionalStreamId(
       session->transport_version(), 0);
-  session->SendRstStream(stream_id, QUIC_STREAM_CANCELLED, 0);
+  QuicStream* stream = session->GetOrCreateStream(stream_id);
+  session->SendRstStream(stream_id, QUIC_STREAM_CANCELLED,
+                         stream->stream_bytes_written());
   return ret;
 }