Do not accept fin that would decrease the stream's final offset.

gfe-relnote: protected by gfe2_reloadable_flag_quic_no_decrease_in_final_offset
PiperOrigin-RevId: 276115511
Change-Id: I6c6fa7e6f8a6374aceb7ef0c19756ea3928479c9
diff --git a/quic/core/quic_stream_sequencer.cc b/quic/core/quic_stream_sequencer.cc
index 8f84fb9..ad07830 100644
--- a/quic/core/quic_stream_sequencer.cc
+++ b/quic/core/quic_stream_sequencer.cc
@@ -9,6 +9,7 @@
 #include <string>
 #include <utility>
 
+#include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
 #include "net/third_party/quiche/src/quic/core/quic_packets.h"
 #include "net/third_party/quiche/src/quic/core/quic_stream.h"
 #include "net/third_party/quiche/src/quic/core/quic_stream_sequencer_buffer.h"
@@ -26,6 +27,7 @@
 QuicStreamSequencer::QuicStreamSequencer(StreamInterface* quic_stream)
     : stream_(quic_stream),
       buffered_frames_(kStreamReceiveWindowLimit),
+      highest_offset_(0),
       close_offset_(std::numeric_limits<QuicStreamOffset>::max()),
       blocked_(false),
       num_frames_received_(0),
@@ -66,6 +68,7 @@
 void QuicStreamSequencer::OnFrameData(QuicStreamOffset byte_offset,
                                       size_t data_len,
                                       const char* data_buffer) {
+  highest_offset_ = std::max(highest_offset_, byte_offset + data_len);
   const size_t previous_readable_bytes = buffered_frames_.ReadableBytes();
   size_t bytes_written;
   std::string error_details;
@@ -123,10 +126,33 @@
 
   // If there is a scheduled close, the new offset should match it.
   if (close_offset_ != kMaxOffset && offset != close_offset_) {
-    stream_->Reset(QUIC_MULTIPLE_TERMINATION_OFFSETS);
+    if (!GetQuicReloadableFlag(quic_no_decrease_in_final_offset)) {
+      stream_->Reset(QUIC_MULTIPLE_TERMINATION_OFFSETS);
+      return false;
+    }
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_no_decrease_in_final_offset, 1, 2);
+    stream_->CloseConnectionWithDetails(
+        QUIC_STREAM_SEQUENCER_INVALID_STATE,
+        QuicStrCat("Stream ", stream_->id(),
+                   " received new final offset: ", offset,
+                   ", which is different from close offset: ", close_offset_));
     return false;
   }
 
+  // The final offset should be no less than the highest offset that is
+  // received.
+  if (GetQuicReloadableFlag(quic_no_decrease_in_final_offset)) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_no_decrease_in_final_offset, 2, 2);
+    if (offset < highest_offset_) {
+      stream_->CloseConnectionWithDetails(
+          QUIC_STREAM_SEQUENCER_INVALID_STATE,
+          QuicStrCat(
+              "Stream ", stream_->id(), " received fin with offset: ", offset,
+              ", which reduces current highest offset: ", highest_offset_));
+      return false;
+    }
+  }
+
   close_offset_ = offset;
 
   MaybeCloseStream();