No public description

PiperOrigin-RevId: 604325619
diff --git a/build/source_list.bzl b/build/source_list.bzl
index 7c1fbdc..e904ef4 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -209,6 +209,7 @@
     "quic/core/frames/quic_path_challenge_frame.h",
     "quic/core/frames/quic_path_response_frame.h",
     "quic/core/frames/quic_ping_frame.h",
+    "quic/core/frames/quic_reset_stream_at_frame.h",
     "quic/core/frames/quic_retire_connection_id_frame.h",
     "quic/core/frames/quic_rst_stream_frame.h",
     "quic/core/frames/quic_stop_sending_frame.h",
@@ -548,6 +549,7 @@
     "quic/core/frames/quic_path_challenge_frame.cc",
     "quic/core/frames/quic_path_response_frame.cc",
     "quic/core/frames/quic_ping_frame.cc",
+    "quic/core/frames/quic_reset_stream_at_frame.cc",
     "quic/core/frames/quic_retire_connection_id_frame.cc",
     "quic/core/frames/quic_rst_stream_frame.cc",
     "quic/core/frames/quic_stop_sending_frame.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index caddc96..f27df5b 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -209,6 +209,7 @@
     "src/quiche/quic/core/frames/quic_path_challenge_frame.h",
     "src/quiche/quic/core/frames/quic_path_response_frame.h",
     "src/quiche/quic/core/frames/quic_ping_frame.h",
+    "src/quiche/quic/core/frames/quic_reset_stream_at_frame.h",
     "src/quiche/quic/core/frames/quic_retire_connection_id_frame.h",
     "src/quiche/quic/core/frames/quic_rst_stream_frame.h",
     "src/quiche/quic/core/frames/quic_stop_sending_frame.h",
@@ -548,6 +549,7 @@
     "src/quiche/quic/core/frames/quic_path_challenge_frame.cc",
     "src/quiche/quic/core/frames/quic_path_response_frame.cc",
     "src/quiche/quic/core/frames/quic_ping_frame.cc",
+    "src/quiche/quic/core/frames/quic_reset_stream_at_frame.cc",
     "src/quiche/quic/core/frames/quic_retire_connection_id_frame.cc",
     "src/quiche/quic/core/frames/quic_rst_stream_frame.cc",
     "src/quiche/quic/core/frames/quic_stop_sending_frame.cc",
diff --git a/build/source_list.json b/build/source_list.json
index 69f3934..0f63fbd 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -208,6 +208,7 @@
     "quiche/quic/core/frames/quic_path_challenge_frame.h",
     "quiche/quic/core/frames/quic_path_response_frame.h",
     "quiche/quic/core/frames/quic_ping_frame.h",
+    "quiche/quic/core/frames/quic_reset_stream_at_frame.h",
     "quiche/quic/core/frames/quic_retire_connection_id_frame.h",
     "quiche/quic/core/frames/quic_rst_stream_frame.h",
     "quiche/quic/core/frames/quic_stop_sending_frame.h",
@@ -547,6 +548,7 @@
     "quiche/quic/core/frames/quic_path_challenge_frame.cc",
     "quiche/quic/core/frames/quic_path_response_frame.cc",
     "quiche/quic/core/frames/quic_ping_frame.cc",
+    "quiche/quic/core/frames/quic_reset_stream_at_frame.cc",
     "quiche/quic/core/frames/quic_retire_connection_id_frame.cc",
     "quiche/quic/core/frames/quic_rst_stream_frame.cc",
     "quiche/quic/core/frames/quic_stop_sending_frame.cc",
diff --git a/quiche/quic/core/chlo_extractor.cc b/quiche/quic/core/chlo_extractor.cc
index ae4d6e3..800efbd 100644
--- a/quiche/quic/core/chlo_extractor.cc
+++ b/quiche/quic/core/chlo_extractor.cc
@@ -12,6 +12,7 @@
 #include "quiche/quic/core/crypto/quic_decrypter.h"
 #include "quiche/quic/core/crypto/quic_encrypter.h"
 #include "quiche/quic/core/frames/quic_ack_frequency_frame.h"
+#include "quiche/quic/core/frames/quic_reset_stream_at_frame.h"
 #include "quiche/quic/core/quic_framer.h"
 #include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/core/quic_utils.h"
@@ -78,6 +79,7 @@
   bool OnMessageFrame(const QuicMessageFrame& frame) override;
   bool OnHandshakeDoneFrame(const QuicHandshakeDoneFrame& frame) override;
   bool OnAckFrequencyFrame(const QuicAckFrequencyFrame& farme) override;
+  bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) override;
   void OnPacketComplete() override {}
   bool IsValidStatelessResetToken(
       const StatelessResetToken& token) const override;
@@ -207,6 +209,11 @@
   return true;
 }
 
+bool ChloFramerVisitor::OnResetStreamAtFrame(
+    const QuicResetStreamAtFrame& /*frame*/) {
+  return true;
+}
+
 bool ChloFramerVisitor::OnAckRange(QuicPacketNumber /*start*/,
                                    QuicPacketNumber /*end*/) {
   return true;
diff --git a/quiche/quic/core/frames/quic_frame.cc b/quiche/quic/core/frames/quic_frame.cc
index df4d9d3..b0bf329 100644
--- a/quiche/quic/core/frames/quic_frame.cc
+++ b/quiche/quic/core/frames/quic_frame.cc
@@ -5,7 +5,9 @@
 #include "quiche/quic/core/frames/quic_frame.h"
 
 #include "quiche/quic/core/frames/quic_new_connection_id_frame.h"
+#include "quiche/quic/core/frames/quic_reset_stream_at_frame.h"
 #include "quiche/quic/core/frames/quic_retire_connection_id_frame.h"
+#include "quiche/quic/core/frames/quic_rst_stream_frame.h"
 #include "quiche/quic/core/quic_constants.h"
 #include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/platform/api/quic_bug_tracker.h"
@@ -80,6 +82,9 @@
 QuicFrame::QuicFrame(QuicAckFrequencyFrame* frame)
     : type(ACK_FREQUENCY_FRAME), ack_frequency_frame(frame) {}
 
+QuicFrame::QuicFrame(QuicResetStreamAtFrame* frame)
+    : type(RESET_STREAM_AT_FRAME), reset_stream_at_frame(frame) {}
+
 void DeleteFrames(QuicFrames* frames) {
   for (QuicFrame& frame : *frames) {
     DeleteFrame(&frame);
@@ -148,6 +153,9 @@
     case ACK_FREQUENCY_FRAME:
       delete frame->ack_frequency_frame;
       break;
+    case RESET_STREAM_AT_FRAME:
+      delete frame->reset_stream_at_frame;
+      break;
     case NUM_FRAME_TYPES:
       QUICHE_DCHECK(false) << "Cannot delete type: " << frame->type;
   }
@@ -179,6 +187,7 @@
     case HANDSHAKE_DONE_FRAME:
     case ACK_FREQUENCY_FRAME:
     case NEW_TOKEN_FRAME:
+    case RESET_STREAM_AT_FRAME:
       return true;
     default:
       return false;
@@ -213,6 +222,8 @@
       return frame.ack_frequency_frame->control_frame_id;
     case NEW_TOKEN_FRAME:
       return frame.new_token_frame->control_frame_id;
+    case RESET_STREAM_AT_FRAME:
+      return frame.reset_stream_at_frame->control_frame_id;
     default:
       return kInvalidControlFrameId;
   }
@@ -259,6 +270,9 @@
     case NEW_TOKEN_FRAME:
       frame->new_token_frame->control_frame_id = control_frame_id;
       return;
+    case RESET_STREAM_AT_FRAME:
+      frame->reset_stream_at_frame->control_frame_id = control_frame_id;
+      return;
     default:
       QUIC_BUG(quic_bug_12594_1)
           << "Try to set control frame id of a frame without control frame id";
@@ -310,6 +324,10 @@
     case NEW_TOKEN_FRAME:
       copy = QuicFrame(new QuicNewTokenFrame(*frame.new_token_frame));
       break;
+    case RESET_STREAM_AT_FRAME:
+      copy =
+          QuicFrame(new QuicResetStreamAtFrame(*frame.reset_stream_at_frame));
+      break;
     default:
       QUIC_BUG(quic_bug_10533_1)
           << "Try to copy a non-retransmittable control frame: " << frame;
@@ -404,6 +422,10 @@
     case ACK_FREQUENCY_FRAME:
       copy = QuicFrame(new QuicAckFrequencyFrame(*frame.ack_frequency_frame));
       break;
+    case RESET_STREAM_AT_FRAME:
+      copy =
+          QuicFrame(new QuicResetStreamAtFrame(*frame.reset_stream_at_frame));
+      break;
     default:
       QUIC_BUG(quic_bug_10533_2) << "Cannot copy frame: " << frame;
       copy = QuicFrame(QuicPingFrame(kInvalidControlFrameId));
@@ -506,6 +528,9 @@
     case ACK_FREQUENCY_FRAME:
       os << "type { ACK_FREQUENCY_FRAME } " << *(frame.ack_frequency_frame);
       break;
+    case RESET_STREAM_AT_FRAME:
+      os << "type { RESET_STREAM_AT_FRAME } " << *(frame.reset_stream_at_frame);
+      break;
     default: {
       QUIC_LOG(ERROR) << "Unknown frame type: " << frame.type;
       break;
diff --git a/quiche/quic/core/frames/quic_frame.h b/quiche/quic/core/frames/quic_frame.h
index 289b08d..dc99fc4 100644
--- a/quiche/quic/core/frames/quic_frame.h
+++ b/quiche/quic/core/frames/quic_frame.h
@@ -5,9 +5,10 @@
 #ifndef QUICHE_QUIC_CORE_FRAMES_QUIC_FRAME_H_
 #define QUICHE_QUIC_CORE_FRAMES_QUIC_FRAME_H_
 
+#include <cstddef>
 #include <ostream>
+#include <string>
 #include <type_traits>
-#include <vector>
 
 #include "absl/container/inlined_vector.h"
 #include "quiche/quic/core/frames/quic_ack_frame.h"
@@ -26,6 +27,7 @@
 #include "quiche/quic/core/frames/quic_path_challenge_frame.h"
 #include "quiche/quic/core/frames/quic_path_response_frame.h"
 #include "quiche/quic/core/frames/quic_ping_frame.h"
+#include "quiche/quic/core/frames/quic_reset_stream_at_frame.h"
 #include "quiche/quic/core/frames/quic_retire_connection_id_frame.h"
 #include "quiche/quic/core/frames/quic_rst_stream_frame.h"
 #include "quiche/quic/core/frames/quic_stop_sending_frame.h"
@@ -34,7 +36,8 @@
 #include "quiche/quic/core/frames/quic_streams_blocked_frame.h"
 #include "quiche/quic/core/frames/quic_window_update_frame.h"
 #include "quiche/quic/core/quic_types.h"
-#include "quiche/quic/platform/api/quic_export.h"
+#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/common/quiche_buffer_allocator.h"
 
 #ifndef QUIC_FRAME_DEBUG
 #if !defined(NDEBUG) || defined(ADDRESS_SANITIZER)
@@ -73,6 +76,7 @@
   explicit QuicFrame(QuicMessageFrame* message_frame);
   explicit QuicFrame(QuicCryptoFrame* crypto_frame);
   explicit QuicFrame(QuicAckFrequencyFrame* ack_frequency_frame);
+  explicit QuicFrame(QuicResetStreamAtFrame* reset_stream_at_frame);
 
   QUICHE_EXPORT friend std::ostream& operator<<(std::ostream& os,
                                                 const QuicFrame& frame);
@@ -115,6 +119,7 @@
         QuicCryptoFrame* crypto_frame;
         QuicAckFrequencyFrame* ack_frequency_frame;
         QuicNewTokenFrame* new_token_frame;
+        QuicResetStreamAtFrame* reset_stream_at_frame;
       };
     };
   };
diff --git a/quiche/quic/core/frames/quic_frames_test.cc b/quiche/quic/core/frames/quic_frames_test.cc
index 671e772..d585c68 100644
--- a/quiche/quic/core/frames/quic_frames_test.cc
+++ b/quiche/quic/core/frames/quic_frames_test.cc
@@ -616,6 +616,9 @@
       case ACK_FREQUENCY_FRAME:
         frames.push_back(QuicFrame(new QuicAckFrequencyFrame()));
         break;
+      case RESET_STREAM_AT_FRAME:
+        frames.push_back(QuicFrame(new QuicResetStreamAtFrame()));
+        break;
       default:
         ASSERT_TRUE(false)
             << "Please fix CopyQuicFrames if a new frame type is added.";
diff --git a/quiche/quic/core/frames/quic_reset_stream_at_frame.cc b/quiche/quic/core/frames/quic_reset_stream_at_frame.cc
new file mode 100644
index 0000000..c8009c5
--- /dev/null
+++ b/quiche/quic/core/frames/quic_reset_stream_at_frame.cc
@@ -0,0 +1,44 @@
+// Copyright 2024 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/quic/core/frames/quic_reset_stream_at_frame.h"
+
+#include <cstdint>
+#include <ostream>
+
+#include "quiche/quic/core/quic_types.h"
+
+namespace quic {
+
+QuicResetStreamAtFrame::QuicResetStreamAtFrame(
+    QuicControlFrameId control_frame_id, QuicStreamId stream_id, uint64_t error,
+    QuicStreamOffset final_offset, QuicStreamOffset reliable_offset)
+    : control_frame_id(control_frame_id),
+      stream_id(stream_id),
+      error(error),
+      final_offset(final_offset),
+      reliable_offset(reliable_offset) {}
+
+std::ostream& operator<<(std::ostream& os,
+                         const QuicResetStreamAtFrame& frame) {
+  os << "{ control_frame_id: " << frame.control_frame_id
+     << ", stream_id: " << frame.stream_id << ", error_code: " << frame.error
+     << ", final_offset: " << frame.final_offset
+     << ", reliable_offset: " << frame.reliable_offset << " }\n";
+  return os;
+}
+
+bool QuicResetStreamAtFrame::operator==(
+    const QuicResetStreamAtFrame& rhs) const {
+  return control_frame_id == rhs.control_frame_id &&
+         stream_id == rhs.stream_id && error == rhs.error &&
+         final_offset == rhs.final_offset &&
+         reliable_offset == rhs.reliable_offset;
+}
+bool QuicResetStreamAtFrame::operator!=(
+    const QuicResetStreamAtFrame& rhs) const {
+  return !(*this == rhs);
+}
+
+}  // namespace quic
diff --git a/quiche/quic/core/frames/quic_reset_stream_at_frame.h b/quiche/quic/core/frames/quic_reset_stream_at_frame.h
new file mode 100644
index 0000000..95e7ee9
--- /dev/null
+++ b/quiche/quic/core/frames/quic_reset_stream_at_frame.h
@@ -0,0 +1,49 @@
+// Copyright 2024 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_FRAMES_QUIC_RESET_STREAM_AT_FRAME_H_
+#define QUICHE_QUIC_CORE_FRAMES_QUIC_RESET_STREAM_AT_FRAME_H_
+
+#include <cstdint>
+#include <ostream>
+
+#include "quiche/quic/core/quic_constants.h"
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/common/platform/api/quiche_export.h"
+
+namespace quic {
+
+// RESET_STREAM_AT allows a QUIC application to reset a stream, but only after
+// the receiver consumes data up to a certain point. Defined in
+// <https://datatracker.ietf.org/doc/draft-ietf-quic-reliable-stream-reset/>.
+struct QUICHE_EXPORT QuicResetStreamAtFrame {
+  QuicResetStreamAtFrame() = default;
+  QuicResetStreamAtFrame(QuicControlFrameId control_frame_id,
+                         QuicStreamId stream_id, uint64_t error,
+                         QuicStreamOffset final_offset,
+                         QuicStreamOffset reliable_offset);
+
+  friend QUICHE_EXPORT std::ostream& operator<<(
+      std::ostream& os, const QuicResetStreamAtFrame& frame);
+
+  bool operator==(const QuicResetStreamAtFrame& rhs) const;
+  bool operator!=(const QuicResetStreamAtFrame& rhs) const;
+
+  // A unique identifier of this control frame. 0 when this frame is received,
+  // and non-zero when sent.
+  QuicControlFrameId control_frame_id = kInvalidControlFrameId;
+
+  QuicStreamId stream_id = 0;
+  uint64_t error = 0;
+
+  // The total number of bytes ever sent on the stream; used for flow control.
+  QuicStreamOffset final_offset = 0;
+  // The RESET_STREAM is active only after the application reads up to
+  // `reliable_offset` bytes.
+  QuicStreamOffset reliable_offset = 0;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_FRAMES_QUIC_RESET_STREAM_AT_FRAME_H_
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index 88f52d8..f1d65b9 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -2120,6 +2120,25 @@
   return true;
 }
 
+bool QuicConnection::OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) {
+  QUIC_BUG_IF(OnResetStreamAtFrame_connection_closed, !connected_)
+      << "Processing RESET_STREAM_AT frame while the connection is closed. "
+         "Received packet info: "
+      << last_received_packet_info_;
+
+  if (debug_visitor_ != nullptr) {
+    debug_visitor_->OnResetStreamAtFrame(frame);
+  }
+  if (!UpdatePacketContent(RESET_STREAM_AT_FRAME)) {
+    return false;
+  }
+
+  // TODO(b/278878322): implement.
+
+  MaybeUpdateAckTimeout();
+  return true;
+}
+
 bool QuicConnection::OnBlockedFrame(const QuicBlockedFrame& frame) {
   QUIC_BUG_IF(quic_bug_12714_17, !connected_)
       << "Processing BLOCKED frame when connection is closed. Received packet "
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h
index 14033bd..598a11e 100644
--- a/quiche/quic/core/quic_connection.h
+++ b/quiche/quic/core/quic_connection.h
@@ -34,6 +34,7 @@
 #include "quiche/quic/core/frames/quic_ack_frequency_frame.h"
 #include "quiche/quic/core/frames/quic_max_streams_frame.h"
 #include "quiche/quic/core/frames/quic_new_connection_id_frame.h"
+#include "quiche/quic/core/frames/quic_reset_stream_at_frame.h"
 #include "quiche/quic/core/quic_alarm.h"
 #include "quiche/quic/core/quic_alarm_factory.h"
 #include "quiche/quic/core/quic_blocked_writer_interface.h"
@@ -424,6 +425,9 @@
   // Called when an AckFrequencyFrame has been parsed.
   virtual void OnAckFrequencyFrame(const QuicAckFrequencyFrame& /*frame*/) {}
 
+  // Called when a ResetStreamAtFrame has been parsed.
+  virtual void OnResetStreamAtFrame(const QuicResetStreamAtFrame& /*frame*/) {}
+
   // Called when |count| packet numbers have been skipped.
   virtual void OnNPacketNumbersSkipped(QuicPacketCount /*count*/,
                                        QuicTime /*now*/) {}
@@ -713,6 +717,7 @@
   bool OnMessageFrame(const QuicMessageFrame& frame) override;
   bool OnHandshakeDoneFrame(const QuicHandshakeDoneFrame& frame) override;
   bool OnAckFrequencyFrame(const QuicAckFrequencyFrame& frame) override;
+  bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) override;
   void OnPacketComplete() override;
   bool IsValidStatelessResetToken(
       const StatelessResetToken& token) const override;
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index bbdb4f4..b767764 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -15376,10 +15376,14 @@
   QuicMessageFrame message_frame;
   QuicNewTokenFrame new_token_frame;
   QuicAckFrequencyFrame ack_frequency_frame;
+  QuicResetStreamAtFrame reset_stream_at_frame;
   QuicBlockedFrame blocked_frame;
   size_t packet_number = 1;
 
   connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+  QuicFramer* framer = const_cast<QuicFramer*>(&connection_.framer());
+  framer->set_process_reset_stream_at(true);
+  peer_framer_.set_process_reset_stream_at(true);
 
   for (uint8_t i = 0; i < NUM_FRAME_TYPES; ++i) {
     QuicFrameType frame_type = static_cast<QuicFrameType>(i);
@@ -15463,6 +15467,9 @@
       case ACK_FREQUENCY_FRAME:
         frame = QuicFrame(&ack_frequency_frame);
         break;
+      case RESET_STREAM_AT_FRAME:
+        frame = QuicFrame(&reset_stream_at_frame);
+        break;
       case NUM_FRAME_TYPES:
         skipped = true;
         break;
diff --git a/quiche/quic/core/quic_framer.cc b/quiche/quic/core/quic_framer.cc
index 8f3a736..d4bb1bc 100644
--- a/quiche/quic/core/quic_framer.cc
+++ b/quiche/quic/core/quic_framer.cc
@@ -19,6 +19,7 @@
 #include "absl/base/macros.h"
 #include "absl/base/optimization.h"
 #include "absl/cleanup/cleanup.h"
+#include "absl/status/status.h"
 #include "absl/strings/escaping.h"
 #include "absl/strings/numbers.h"
 #include "absl/strings/str_cat.h"
@@ -35,6 +36,7 @@
 #include "quiche/quic/core/crypto/quic_encrypter.h"
 #include "quiche/quic/core/crypto/quic_random.h"
 #include "quiche/quic/core/frames/quic_ack_frequency_frame.h"
+#include "quiche/quic/core/frames/quic_reset_stream_at_frame.h"
 #include "quiche/quic/core/quic_connection_context.h"
 #include "quiche/quic/core/quic_connection_id.h"
 #include "quiche/quic/core/quic_constants.h"
@@ -56,6 +58,7 @@
 #include "quiche/quic/platform/api/quic_logging.h"
 #include "quiche/quic/platform/api/quic_stack_trace.h"
 #include "quiche/common/quiche_text_utils.h"
+#include "quiche/common/wire_serialization.h"
 
 namespace quic {
 
@@ -401,6 +404,7 @@
       process_timestamps_(false),
       max_receive_timestamps_per_ack_(std::numeric_limits<uint32_t>::max()),
       receive_timestamps_exponent_(0),
+      process_reset_stream_at_(false),
       creation_time_(creation_time),
       last_timestamp_(QuicTime::Delta::Zero()),
       support_key_update_for_connection_(false),
@@ -625,6 +629,16 @@
 }
 
 // static
+size_t QuicFramer::GetResetStreamAtFrameSize(
+    const QuicResetStreamAtFrame& frame) {
+  return QuicDataWriter::GetVarInt62Len(IETF_RESET_STREAM_AT) +
+         QuicDataWriter::GetVarInt62Len(frame.stream_id) +
+         QuicDataWriter::GetVarInt62Len(frame.error) +
+         QuicDataWriter::GetVarInt62Len(frame.final_offset) +
+         QuicDataWriter::GetVarInt62Len(frame.reliable_offset);
+}
+
+// static
 size_t QuicFramer::GetPathChallengeFrameSize(
     const QuicPathChallengeFrame& frame) {
   return kQuicFrameTypeSize + sizeof(frame.data_buffer);
@@ -679,6 +693,8 @@
       return kQuicFrameTypeSize;
     case ACK_FREQUENCY_FRAME:
       return GetAckFrequencyFrameSize(*frame.ack_frequency_frame);
+    case RESET_STREAM_AT_FRAME:
+      return GetResetStreamAtFrameSize(*frame.reset_stream_at_frame);
     case STREAM_FRAME:
     case ACK_FRAME:
     case STOP_WAITING_FRAME:
@@ -1193,6 +1209,17 @@
           return 0;
         }
         break;
+      case RESET_STREAM_AT_FRAME:
+        QUIC_BUG_IF(reset_stream_at_appended_while_disabled,
+                    !process_reset_stream_at_)
+            << "Requested serialization of RESET_STREAM_AT_FRAME while it is "
+               "not explicitly enabled in the framer";
+        if (!AppendResetFrameAtFrame(*frame.reset_stream_at_frame, *writer)) {
+          QUIC_BUG(cannot_append_reset_stream_at)
+              << "AppendResetStreamAtFram failed: " << detailed_error();
+          return 0;
+        }
+        break;
       default:
         set_detailed_error("Tried to append unknown frame type.");
         RaiseError(QUIC_INVALID_FRAME_DATA);
@@ -3126,6 +3153,24 @@
           }
           break;
         }
+        case IETF_RESET_STREAM_AT: {
+          if (!process_reset_stream_at_) {
+            set_detailed_error("RESET_STREAM_AT not enabled.");
+            return RaiseError(QUIC_INVALID_FRAME_DATA);
+          }
+          QuicResetStreamAtFrame frame;
+          if (!ProcessResetStreamAtFrame(*reader, frame)) {
+            return RaiseError(QUIC_INVALID_FRAME_DATA);
+          }
+          QUIC_DVLOG(2) << ENDPOINT << "Processing RESET_STREAM_AT frame "
+                        << frame;
+          if (!visitor_->OnResetStreamAtFrame(frame)) {
+            QUIC_DVLOG(1) << "Visitor asked to stop further processing.";
+            // Returning true since there was no parsing error.
+            return true;
+          }
+          break;
+        }
         default:
           set_detailed_error("Illegal frame type.");
           QUIC_DLOG(WARNING)
@@ -3345,6 +3390,31 @@
   return true;
 }
 
+bool QuicFramer::ProcessResetStreamAtFrame(QuicDataReader& reader,
+                                           QuicResetStreamAtFrame& frame) {
+  if (!ReadUint32FromVarint62(&reader, IETF_RESET_STREAM_AT,
+                              &frame.stream_id)) {
+    return false;
+  }
+  if (!reader.ReadVarInt62(&frame.error)) {
+    set_detailed_error("Failed to read the error code.");
+    return false;
+  }
+  if (!reader.ReadVarInt62(&frame.final_offset)) {
+    set_detailed_error("Failed to read the final offset.");
+    return false;
+  }
+  if (!reader.ReadVarInt62(&frame.reliable_offset)) {
+    set_detailed_error("Failed to read the reliable offset.");
+    return false;
+  }
+  if (frame.reliable_offset > frame.final_offset) {
+    set_detailed_error("reliable_offset > final_offset");
+    return false;
+  }
+  return true;
+}
+
 bool QuicFramer::ProcessAckFrame(QuicDataReader* reader, uint8_t frame_type) {
   const bool has_ack_blocks =
       ExtractBit(frame_type, kQuicHasMultipleAckBlocksOffset);
@@ -4951,6 +5021,9 @@
     case ACK_FREQUENCY_FRAME:
       type_byte = IETF_ACK_FREQUENCY;
       break;
+    case RESET_STREAM_AT_FRAME:
+      type_byte = IETF_RESET_STREAM_AT;
+      break;
     default:
       QUIC_BUG(quic_bug_10850_75)
           << "Attempt to generate a frame type for an unsupported value: "
@@ -5194,6 +5267,26 @@
   return true;
 }
 
+bool QuicFramer::AppendResetFrameAtFrame(const QuicResetStreamAtFrame& frame,
+                                         QuicDataWriter& writer) {
+  if (frame.reliable_offset > frame.final_offset) {
+    QUIC_BUG(AppendResetFrameAtFrame_offset_mismatch)
+        << "reliable_offset > final_offset";
+    set_detailed_error("reliable_offset > final_offset");
+    return false;
+  }
+  absl::Status status =
+      quiche::SerializeIntoWriter(writer, quiche::WireVarInt62(frame.stream_id),
+                                  quiche::WireVarInt62(frame.error),
+                                  quiche::WireVarInt62(frame.final_offset),
+                                  quiche::WireVarInt62(frame.reliable_offset));
+  if (!status.ok()) {
+    set_detailed_error(std::string(status.message()));
+    return false;
+  }
+  return true;
+}
+
 void QuicFramer::set_version(const ParsedQuicVersion version) {
   QUICHE_DCHECK(IsSupportedVersion(version))
       << ParsedQuicVersionToString(version);
diff --git a/quiche/quic/core/quic_framer.h b/quiche/quic/core/quic_framer.h
index 432d052..5e54bb1 100644
--- a/quiche/quic/core/quic_framer.h
+++ b/quiche/quic/core/quic_framer.h
@@ -15,6 +15,7 @@
 #include "quiche/quic/core/crypto/quic_decrypter.h"
 #include "quiche/quic/core/crypto/quic_encrypter.h"
 #include "quiche/quic/core/crypto/quic_random.h"
+#include "quiche/quic/core/frames/quic_reset_stream_at_frame.h"
 #include "quiche/quic/core/quic_connection_id.h"
 #include "quiche/quic/core/quic_packets.h"
 #include "quiche/quic/core/quic_types.h"
@@ -222,6 +223,9 @@
   // Called when an AckFrequencyFrame has been parsed.
   virtual bool OnAckFrequencyFrame(const QuicAckFrequencyFrame& frame) = 0;
 
+  // Called when an ResetStreamAtFrame has been parsed.
+  virtual bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) = 0;
+
   // Called when a packet has been completely processed.
   virtual void OnPacketComplete() = 0;
 
@@ -324,6 +328,11 @@
     receive_timestamps_exponent_ = exponent;
   }
 
+  // Allows enabling RESET_STREAM_AT frame processing.
+  void set_process_reset_stream_at(bool process_reset_stream_at) {
+    process_reset_stream_at_ = process_reset_stream_at;
+  }
+
   // Pass a UDP packet into the framer for parsing.
   // Return true if the packet was processed successfully. |packet| must be a
   // single, complete UDP packet (not a frame of a packet).  This packet
@@ -360,6 +369,8 @@
                                       const QuicRstStreamFrame& frame);
   // Size in bytes of all ack frenquency frame fields.
   static size_t GetAckFrequencyFrameSize(const QuicAckFrequencyFrame& frame);
+  // Size in bytes of all RESET_STREAM_AT frame fields.
+  static size_t GetResetStreamAtFrameSize(const QuicResetStreamAtFrame& frame);
   // Size in bytes of all connection close frame fields, including the error
   // details.
   static size_t GetConnectionCloseFrameSize(
@@ -539,6 +550,8 @@
   bool AppendCryptoFrame(const QuicCryptoFrame& frame, QuicDataWriter* writer);
   bool AppendAckFrequencyFrame(const QuicAckFrequencyFrame& frame,
                                QuicDataWriter* writer);
+  bool AppendResetFrameAtFrame(const QuicResetStreamAtFrame& frame,
+                               QuicDataWriter& writer);
 
   // SetDecrypter sets the primary decrypter, replacing any that already exists.
   // If an alternative decrypter is in place then the function QUICHE_DCHECKs.
@@ -978,6 +991,8 @@
                           QuicCryptoFrame* frame);
   bool ProcessAckFrequencyFrame(QuicDataReader* reader,
                                 QuicAckFrequencyFrame* frame);
+  bool ProcessResetStreamAtFrame(QuicDataReader& reader,
+                                 QuicResetStreamAtFrame& frame);
   // IETF frame appending methods.  All methods append the type byte as well.
   bool AppendIetfStreamFrame(const QuicStreamFrame& frame,
                              bool last_frame_in_packet, QuicDataWriter* writer);
@@ -1112,6 +1127,8 @@
   mutable uint32_t max_receive_timestamps_per_ack_;
   // The exponent to use when writing/reading ACK receive timestamps.
   mutable uint32_t receive_timestamps_exponent_;
+  // If true, process RESET_STREAM_AT frames.
+  bool process_reset_stream_at_;
   // The creation time of the connection, used to calculate timestamps.
   QuicTime creation_time_;
   // The last timestamp received if process_timestamps_ is true.
diff --git a/quiche/quic/core/quic_framer_test.cc b/quiche/quic/core/quic_framer_test.cc
index e9dded2..7f4e53a 100644
--- a/quiche/quic/core/quic_framer_test.cc
+++ b/quiche/quic/core/quic_framer_test.cc
@@ -22,6 +22,7 @@
 #include "quiche/quic/core/crypto/null_encrypter.h"
 #include "quiche/quic/core/crypto/quic_decrypter.h"
 #include "quiche/quic/core/crypto/quic_encrypter.h"
+#include "quiche/quic/core/frames/quic_reset_stream_at_frame.h"
 #include "quiche/quic/core/quic_connection_id.h"
 #include "quiche/quic/core/quic_error_codes.h"
 #include "quiche/quic/core/quic_packets.h"
@@ -460,6 +461,15 @@
     return true;
   }
 
+  bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) override {
+    ++frame_count_;
+    reset_stream_at_frames_.push_back(
+        std::make_unique<QuicResetStreamAtFrame>(frame));
+    EXPECT_TRUE(VersionHasIetfQuicFrames(transport_version_));
+    EXPECT_EQ(IETF_RESET_STREAM_AT, framer_->current_received_frame_type());
+    return true;
+  }
+
   void OnPacketComplete() override { ++complete_packets_; }
 
   bool OnRstStreamFrame(const QuicRstStreamFrame& frame) override {
@@ -651,6 +661,7 @@
   std::vector<std::unique_ptr<QuicMessageFrame>> message_frames_;
   std::vector<std::unique_ptr<QuicHandshakeDoneFrame>> handshake_done_frames_;
   std::vector<std::unique_ptr<QuicAckFrequencyFrame>> ack_frequency_frames_;
+  std::vector<std::unique_ptr<QuicResetStreamAtFrame>> reset_stream_at_frames_;
   std::vector<std::unique_ptr<QuicEncryptedPacket>> coalesced_packets_;
   std::vector<std::unique_ptr<QuicEncryptedPacket>> undecryptable_packets_;
   std::vector<EncryptionLevel> undecryptable_decryption_levels_;
@@ -4791,6 +4802,87 @@
   EXPECT_EQ(true, frame->ignore_order);
 }
 
+TEST_P(QuicFramerTest, ParseResetStreamAtFrame) {
+  SetDecrypterLevel(ENCRYPTION_FORWARD_SECURE);
+  // clang-format off
+  unsigned char packet[] = {
+      // type (short header, 4 byte packet number)
+      0x43,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // type = RESET_STREAM_AT
+      0x24,
+      // stream ID
+      0x00,
+      // application error code
+      0x1e,
+      // final size
+      0x20,
+      // reliable size
+      0x10,
+  };
+  // clang-format on
+
+  if (!VersionHasIetfQuicFrames(framer_.transport_version())) {
+    return;
+  }
+  framer_.set_process_reset_stream_at(true);
+
+  QuicEncryptedPacket encrypted(AsChars(packet), ABSL_ARRAYSIZE(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_THAT(framer_.error(), IsQuicNoError());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      kPacket8ByteConnectionId, kPacket0ByteConnectionId));
+
+  ASSERT_EQ(visitor_.reset_stream_at_frames_.size(), 1);
+  const QuicResetStreamAtFrame& frame = *visitor_.reset_stream_at_frames_[0];
+  EXPECT_EQ(frame.stream_id, 0x00);
+  EXPECT_EQ(frame.error, 0x1e);
+  EXPECT_EQ(frame.final_offset, 0x20);
+  EXPECT_EQ(frame.reliable_offset, 0x10);
+}
+
+TEST_P(QuicFramerTest, ParseInvalidResetStreamAtFrame) {
+  SetDecrypterLevel(ENCRYPTION_FORWARD_SECURE);
+  // clang-format off
+  unsigned char packet[] = {
+      // type (short header, 4 byte packet number)
+      0x43,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // type = RESET_STREAM_AT
+      0x24,
+      // stream ID
+      0x00,
+      // application error code
+      0x1e,
+      // final size
+      0x20,
+      // reliable size
+      0x30,
+  };
+  // clang-format on
+
+  if (!VersionHasIetfQuicFrames(framer_.transport_version())) {
+    return;
+  }
+  framer_.set_process_reset_stream_at(true);
+
+  QuicEncryptedPacket encrypted(AsChars(packet), ABSL_ARRAYSIZE(packet), false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(framer_.error(), QUIC_INVALID_FRAME_DATA);
+  EXPECT_EQ(visitor_.reset_stream_at_frames_.size(), 0);
+}
+
 TEST_P(QuicFramerTest, MessageFrame) {
   SetDecrypterLevel(ENCRYPTION_FORWARD_SECURE);
   // clang-format off
@@ -8092,6 +8184,55 @@
       ABSL_ARRAYSIZE(packet));
 }
 
+TEST_P(QuicFramerTest, BuildResetStreamAtPacket) {
+  if (!VersionHasIetfQuicFrames(framer_.transport_version())) {
+    return;
+  }
+
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicResetStreamAtFrame frame;
+  frame.stream_id = 0x00;
+  frame.error = 0x1e;
+  frame.final_offset = 0x20;
+  frame.reliable_offset = 0x10;
+  QuicFrames frames = {QuicFrame(&frame)};
+
+  framer_.set_process_reset_stream_at(true);
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  // clang-format off
+  unsigned char packet[] = {
+      // type (short header, 4 byte packet number)
+      0x43,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // type = RESET_STREAM_AT
+      0x24,
+      // stream ID
+      0x00,
+      // application error code
+      0x1e,
+      // final size
+      0x20,
+      // reliable size
+      0x10,
+  };
+  // clang-format on
+
+  quiche::test::CompareCharArraysWithHexError(
+      "constructed packet", data->data(), data->length(), AsChars(packet),
+      ABSL_ARRAYSIZE(packet));
+}
+
 TEST_P(QuicFramerTest, BuildMessagePacket) {
   QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
   QuicPacketHeader header;
diff --git a/quiche/quic/core/quic_trace_visitor.cc b/quiche/quic/core/quic_trace_visitor.cc
index 7906536..2b049ca 100644
--- a/quiche/quic/core/quic_trace_visitor.cc
+++ b/quiche/quic/core/quic_trace_visitor.cc
@@ -94,6 +94,7 @@
       case MESSAGE_FRAME:
       case CRYPTO_FRAME:
       case NEW_TOKEN_FRAME:
+      case RESET_STREAM_AT_FRAME:
         break;
 
       // Ignore gQUIC-specific frames.
@@ -226,6 +227,7 @@
     case CRYPTO_FRAME:
     case NEW_TOKEN_FRAME:
     case ACK_FREQUENCY_FRAME:
+    case RESET_STREAM_AT_FRAME:
       break;
 
     case NUM_FRAME_TYPES:
diff --git a/quiche/quic/core/quic_types.cc b/quiche/quic/core/quic_types.cc
index b7b2b26..2555384 100644
--- a/quiche/quic/core/quic_types.cc
+++ b/quiche/quic/core/quic_types.cc
@@ -157,6 +157,7 @@
     RETURN_STRING_LITERAL(NEW_TOKEN_FRAME)
     RETURN_STRING_LITERAL(RETIRE_CONNECTION_ID_FRAME)
     RETURN_STRING_LITERAL(ACK_FREQUENCY_FRAME)
+    RETURN_STRING_LITERAL(RESET_STREAM_AT_FRAME)
     RETURN_STRING_LITERAL(NUM_FRAME_TYPES)
   }
   return absl::StrCat("Unknown(", static_cast<int>(t), ")");
diff --git a/quiche/quic/core/quic_types.h b/quiche/quic/core/quic_types.h
index b626531..119b266 100644
--- a/quiche/quic/core/quic_types.h
+++ b/quiche/quic/core/quic_types.h
@@ -275,6 +275,7 @@
   NEW_TOKEN_FRAME,
   RETIRE_CONNECTION_ID_FRAME,
   ACK_FREQUENCY_FRAME,
+  RESET_STREAM_AT_FRAME,
 
   NUM_FRAME_TYPES
 };
@@ -347,6 +348,9 @@
   // packet receive timestamps.
   // TODO(ianswett): Determine a proper value to replace this temporary value.
   IETF_ACK_RECEIVE_TIMESTAMPS = 0x22,
+
+  // https://datatracker.ietf.org/doc/html/draft-ietf-quic-reliable-stream-reset
+  IETF_RESET_STREAM_AT = 0x24,
 };
 QUICHE_EXPORT std::ostream& operator<<(std::ostream& os,
                                        const QuicIetfFrameType& c);
@@ -357,7 +361,7 @@
 #define IETF_STREAM_FRAME_TYPE_MASK 0xfffffffffffffff8
 #define IETF_STREAM_FRAME_FLAG_MASK 0x07
 #define IS_IETF_STREAM_FRAME(_stype_) \
-  (((_stype_)&IETF_STREAM_FRAME_TYPE_MASK) == IETF_STREAM)
+  (((_stype_) & IETF_STREAM_FRAME_TYPE_MASK) == IETF_STREAM)
 
 // These are the values encoded in the low-order 3 bits of the
 // IETF_STREAMx frame type.
diff --git a/quiche/quic/core/quic_unacked_packet_map.cc b/quiche/quic/core/quic_unacked_packet_map.cc
index a89cf80..adebba4 100644
--- a/quiche/quic/core/quic_unacked_packet_map.cc
+++ b/quiche/quic/core/quic_unacked_packet_map.cc
@@ -52,6 +52,7 @@
   kNewTokenFrameBitfield = 1 << 20,
   kRetireConnectionIdFrameBitfield = 1 << 21,
   kAckFrequencyFrameBitfield = 1 << 22,
+  kResetStreamAtFrameBitfield = 1 << 23,
 };
 
 QuicFrameTypeBitfield GetFrameTypeBitfield(QuicFrameType type) {
@@ -102,6 +103,8 @@
       return kRetireConnectionIdFrameBitfield;
     case ACK_FREQUENCY_FRAME:
       return kAckFrequencyFrameBitfield;
+    case RESET_STREAM_AT_FRAME:
+      return kResetStreamAtFrameBitfield;
     case NUM_FRAME_TYPES:
       QUIC_BUG(quic_bug_10518_1) << "Unexpected frame type";
       return kInvalidFrameBitfield;
diff --git a/quiche/quic/core/tls_chlo_extractor.h b/quiche/quic/core/tls_chlo_extractor.h
index c8d7810..2aaa0eb 100644
--- a/quiche/quic/core/tls_chlo_extractor.h
+++ b/quiche/quic/core/tls_chlo_extractor.h
@@ -13,6 +13,7 @@
 #include "absl/types/span.h"
 #include "openssl/ssl.h"
 #include "quiche/quic/core/frames/quic_ack_frequency_frame.h"
+#include "quiche/quic/core/frames/quic_reset_stream_at_frame.h"
 #include "quiche/quic/core/quic_framer.h"
 #include "quiche/quic/core/quic_packets.h"
 #include "quiche/quic/core/quic_stream_sequencer.h"
@@ -177,6 +178,9 @@
   bool OnAckFrequencyFrame(const QuicAckFrequencyFrame& /*frame*/) override {
     return true;
   }
+  bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& /*frame*/) override {
+    return true;
+  }
   void OnPacketComplete() override {}
   bool IsValidStatelessResetToken(
       const StatelessResetToken& /*token*/) const override {
diff --git a/quiche/quic/test_tools/quic_test_utils.cc b/quiche/quic/test_tools/quic_test_utils.cc
index 8ffb8e8..58277df 100644
--- a/quiche/quic/test_tools/quic_test_utils.cc
+++ b/quiche/quic/test_tools/quic_test_utils.cc
@@ -447,6 +447,11 @@
   return true;
 }
 
+bool NoOpFramerVisitor::OnResetStreamAtFrame(
+    const QuicResetStreamAtFrame& /*frame*/) {
+  return true;
+}
+
 bool NoOpFramerVisitor::IsValidStatelessResetToken(
     const StatelessResetToken& /*token*/) const {
   return false;
diff --git a/quiche/quic/test_tools/quic_test_utils.h b/quiche/quic/test_tools/quic_test_utils.h
index 8fdaf98..7de2c2a 100644
--- a/quiche/quic/test_tools/quic_test_utils.h
+++ b/quiche/quic/test_tools/quic_test_utils.h
@@ -20,6 +20,7 @@
 #include "quiche/quic/core/congestion_control/loss_detection_interface.h"
 #include "quiche/quic/core/congestion_control/send_algorithm_interface.h"
 #include "quiche/quic/core/crypto/transport_parameters.h"
+#include "quiche/quic/core/frames/quic_reset_stream_at_frame.h"
 #include "quiche/quic/core/http/http_decoder.h"
 #include "quiche/quic/core/http/quic_server_session_base.h"
 #include "quiche/quic/core/http/quic_spdy_client_session_base.h"
@@ -353,6 +354,8 @@
               (override));
   MOCK_METHOD(bool, OnAckFrequencyFrame, (const QuicAckFrequencyFrame& frame),
               (override));
+  MOCK_METHOD(bool, OnResetStreamAtFrame, (const QuicResetStreamAtFrame& frame),
+              (override));
   MOCK_METHOD(void, OnPacketComplete, (), (override));
   MOCK_METHOD(bool, IsValidStatelessResetToken, (const StatelessResetToken&),
               (const, override));
@@ -420,6 +423,7 @@
   bool OnMessageFrame(const QuicMessageFrame& frame) override;
   bool OnHandshakeDoneFrame(const QuicHandshakeDoneFrame& frame) override;
   bool OnAckFrequencyFrame(const QuicAckFrequencyFrame& frame) override;
+  bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) override;
   void OnPacketComplete() override {}
   bool IsValidStatelessResetToken(
       const StatelessResetToken& token) const override;
diff --git a/quiche/quic/test_tools/simple_quic_framer.cc b/quiche/quic/test_tools/simple_quic_framer.cc
index fc50251..54ae4b3 100644
--- a/quiche/quic/test_tools/simple_quic_framer.cc
+++ b/quiche/quic/test_tools/simple_quic_framer.cc
@@ -11,6 +11,7 @@
 #include "absl/strings/string_view.h"
 #include "quiche/quic/core/crypto/quic_decrypter.h"
 #include "quiche/quic/core/crypto/quic_encrypter.h"
+#include "quiche/quic/core/frames/quic_reset_stream_at_frame.h"
 #include "quiche/quic/core/quic_types.h"
 
 namespace quic {
@@ -210,6 +211,11 @@
     return true;
   }
 
+  bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) override {
+    reset_stream_at_frames_.push_back(frame);
+    return true;
+  }
+
   void OnPacketComplete() override {}
 
   bool IsValidStatelessResetToken(
@@ -312,6 +318,7 @@
   std::vector<QuicMessageFrame> message_frames_;
   std::vector<QuicHandshakeDoneFrame> handshake_done_frames_;
   std::vector<QuicAckFrequencyFrame> ack_frequency_frames_;
+  std::vector<QuicResetStreamAtFrame> reset_stream_at_frames_;
   std::vector<std::unique_ptr<std::string>> stream_data_;
   std::vector<std::unique_ptr<std::string>> crypto_data_;
   EncryptionLevel last_decrypted_level_;
diff --git a/quiche/quic/tools/quic_packet_printer_bin.cc b/quiche/quic/tools/quic_packet_printer_bin.cc
index e507c5e..5ed7701 100644
--- a/quiche/quic/tools/quic_packet_printer_bin.cc
+++ b/quiche/quic/tools/quic_packet_printer_bin.cc
@@ -213,6 +213,10 @@
     std::cerr << "OnAckFrequencyFrame: " << frame;
     return true;
   }
+  bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) override {
+    std::cerr << "OnResetStreamAtFrame: " << frame;
+    return true;
+  }
   void OnPacketComplete() override { std::cerr << "OnPacketComplete\n"; }
   bool IsValidStatelessResetToken(
       const StatelessResetToken& /*token*/) const override {