Support IETF QUIC reset error codes.

Currently, we use gQUIC error codes everywhere, and then map gQUIC to IETF before sending it on the wire.  This CL introduces QuicResetErrorCode, a (gQUIC, IETF) error code tuple that is passed around instead.

Protected by FLAGS_quic_reloadable_flag_quic_match_ietf_reset_code.

PiperOrigin-RevId: 398129312
diff --git a/quic/core/frames/quic_frames_test.cc b/quic/core/frames/quic_frames_test.cc
index 038924e..43eabf7 100644
--- a/quic/core/frames/quic_frames_test.cc
+++ b/quic/core/frames/quic_frames_test.cc
@@ -85,7 +85,8 @@
   std::ostringstream stream;
   stream << rst_stream;
   EXPECT_EQ(
-      "{ control_frame_id: 1, stream_id: 1, byte_offset: 3, error_code: 6 }\n",
+      "{ control_frame_id: 1, stream_id: 1, byte_offset: 3, error_code: 6, "
+      "ietf_error_code: 0 }\n",
       stream.str());
   EXPECT_TRUE(IsControlFrame(frame.type));
 }
diff --git a/quic/core/frames/quic_rst_stream_frame.cc b/quic/core/frames/quic_rst_stream_frame.cc
index d70f217..873868c 100644
--- a/quic/core/frames/quic_rst_stream_frame.cc
+++ b/quic/core/frames/quic_rst_stream_frame.cc
@@ -18,12 +18,23 @@
       ietf_error_code(RstStreamErrorCodeToIetfResetStreamErrorCode(error_code)),
       byte_offset(bytes_written) {}
 
+QuicRstStreamFrame::QuicRstStreamFrame(QuicControlFrameId control_frame_id,
+                                       QuicStreamId stream_id,
+                                       QuicResetStreamError error,
+                                       QuicStreamOffset bytes_written)
+    : control_frame_id(control_frame_id),
+      stream_id(stream_id),
+      error_code(error.internal_code()),
+      ietf_error_code(error.ietf_application_code()),
+      byte_offset(bytes_written) {}
+
 std::ostream& operator<<(std::ostream& os,
                          const QuicRstStreamFrame& rst_frame) {
   os << "{ control_frame_id: " << rst_frame.control_frame_id
      << ", stream_id: " << rst_frame.stream_id
      << ", byte_offset: " << rst_frame.byte_offset
-     << ", error_code: " << rst_frame.error_code << " }\n";
+     << ", error_code: " << rst_frame.error_code
+     << ", ietf_error_code: " << rst_frame.ietf_error_code << " }\n";
   return os;
 }
 
diff --git a/quic/core/frames/quic_rst_stream_frame.h b/quic/core/frames/quic_rst_stream_frame.h
index 9c9e42e..744a227 100644
--- a/quic/core/frames/quic_rst_stream_frame.h
+++ b/quic/core/frames/quic_rst_stream_frame.h
@@ -16,13 +16,14 @@
 struct QUIC_EXPORT_PRIVATE QuicRstStreamFrame {
   QuicRstStreamFrame() = default;
   QuicRstStreamFrame(QuicControlFrameId control_frame_id,
-                     QuicStreamId stream_id,
-                     QuicRstStreamErrorCode error_code,
+                     QuicStreamId stream_id, QuicRstStreamErrorCode error_code,
+                     QuicStreamOffset bytes_written);
+  QuicRstStreamFrame(QuicControlFrameId control_frame_id,
+                     QuicStreamId stream_id, QuicResetStreamError error,
                      QuicStreamOffset bytes_written);
 
   friend QUIC_EXPORT_PRIVATE std::ostream& operator<<(
-      std::ostream& os,
-      const QuicRstStreamFrame& r);
+      std::ostream& os, const QuicRstStreamFrame& r);
 
   // A unique identifier of this control frame. 0 when this frame is received,
   // and non-zero when sent.
@@ -45,6 +46,11 @@
   // that stream. This can be done through normal termination (data packet with
   // FIN) or through a RST.
   QuicStreamOffset byte_offset = 0;
+
+  // Returns a tuple of both |error_code| and |ietf_error_code|.
+  QuicResetStreamError error() const {
+    return QuicResetStreamError(error_code, ietf_error_code);
+  }
 };
 
 }  // namespace quic
diff --git a/quic/core/frames/quic_stop_sending_frame.cc b/quic/core/frames/quic_stop_sending_frame.cc
index 93eed75..396288e 100644
--- a/quic/core/frames/quic_stop_sending_frame.cc
+++ b/quic/core/frames/quic_stop_sending_frame.cc
@@ -11,11 +11,16 @@
 QuicStopSendingFrame::QuicStopSendingFrame(QuicControlFrameId control_frame_id,
                                            QuicStreamId stream_id,
                                            QuicRstStreamErrorCode error_code)
+    : QuicStopSendingFrame(control_frame_id, stream_id,
+                           QuicResetStreamError::FromInternal(error_code)) {}
+
+QuicStopSendingFrame::QuicStopSendingFrame(QuicControlFrameId control_frame_id,
+                                           QuicStreamId stream_id,
+                                           QuicResetStreamError error)
     : control_frame_id(control_frame_id),
       stream_id(stream_id),
-      error_code(error_code),
-      ietf_error_code(
-          RstStreamErrorCodeToIetfResetStreamErrorCode(error_code)) {}
+      error_code(error.internal_code()),
+      ietf_error_code(error.ietf_application_code()) {}
 
 std::ostream& operator<<(std::ostream& os, const QuicStopSendingFrame& frame) {
   os << "{ control_frame_id: " << frame.control_frame_id
diff --git a/quic/core/frames/quic_stop_sending_frame.h b/quic/core/frames/quic_stop_sending_frame.h
index 60158b2..b575e75 100644
--- a/quic/core/frames/quic_stop_sending_frame.h
+++ b/quic/core/frames/quic_stop_sending_frame.h
@@ -18,6 +18,8 @@
   QuicStopSendingFrame(QuicControlFrameId control_frame_id,
                        QuicStreamId stream_id,
                        QuicRstStreamErrorCode error_code);
+  QuicStopSendingFrame(QuicControlFrameId control_frame_id,
+                       QuicStreamId stream_id, QuicResetStreamError error);
 
   friend QUIC_EXPORT_PRIVATE std::ostream& operator<<(
       std::ostream& os,
@@ -35,6 +37,11 @@
 
   // On-the-wire application error code of the frame.
   uint64_t ietf_error_code = 0;
+
+  // Returns a tuple of both |error_code| and |ietf_error_code|.
+  QuicResetStreamError error() const {
+    return QuicResetStreamError(error_code, ietf_error_code);
+  }
 };
 
 }  // namespace quic
diff --git a/quic/core/http/quic_send_control_stream.cc b/quic/core/http/quic_send_control_stream.cc
index 79555e0..92831f2 100644
--- a/quic/core/http/quic_send_control_stream.cc
+++ b/quic/core/http/quic_send_control_stream.cc
@@ -31,7 +31,7 @@
       << "OnStreamReset() called for write unidirectional stream.";
 }
 
-bool QuicSendControlStream::OnStopSending(QuicRstStreamErrorCode /* code */) {
+bool QuicSendControlStream::OnStopSending(QuicResetStreamError /* code */) {
   stream_delegate()->OnStreamError(
       QUIC_HTTP_CLOSED_CRITICAL_STREAM,
       "STOP_SENDING received for send control stream");
diff --git a/quic/core/http/quic_send_control_stream.h b/quic/core/http/quic_send_control_stream.h
index face806..d376a63 100644
--- a/quic/core/http/quic_send_control_stream.h
+++ b/quic/core/http/quic_send_control_stream.h
@@ -31,7 +31,7 @@
   // Overriding QuicStream::OnStopSending() to make sure control stream is never
   // closed before connection.
   void OnStreamReset(const QuicRstStreamFrame& frame) override;
-  bool OnStopSending(QuicRstStreamErrorCode code) override;
+  bool OnStopSending(QuicResetStreamError code) override;
 
   // Send SETTINGS frame if it hasn't been sent yet. Settings frame must be the
   // first frame sent on this stream.
diff --git a/quic/core/http/quic_send_control_stream_test.cc b/quic/core/http/quic_send_control_stream_test.cc
index 3066cb3..45de224 100644
--- a/quic/core/http/quic_send_control_stream_test.cc
+++ b/quic/core/http/quic_send_control_stream_test.cc
@@ -216,7 +216,8 @@
   Initialize();
   EXPECT_CALL(*connection_,
               CloseConnection(QUIC_HTTP_CLOSED_CRITICAL_STREAM, _, _));
-  send_control_stream_->OnStopSending(QUIC_STREAM_CANCELLED);
+  send_control_stream_->OnStopSending(
+      QuicResetStreamError::FromInternal(QUIC_STREAM_CANCELLED));
 }
 
 TEST_P(QuicSendControlStreamTest, ReceiveDataOnSendControlStream) {
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 41dfe71..aa41b3c 100644
--- a/quic/core/http/quic_spdy_server_stream_base_test.cc
+++ b/quic/core/http/quic_spdy_server_stream_base_test.cc
@@ -54,10 +54,15 @@
   stream_->StopReading();
 
   if (session_.version().UsesHttp3()) {
-    EXPECT_CALL(session_, MaybeSendStopSendingFrame(_, QUIC_STREAM_NO_ERROR))
+    EXPECT_CALL(session_,
+                MaybeSendStopSendingFrame(_, QuicResetStreamError::FromInternal(
+                                                 QUIC_STREAM_NO_ERROR)))
         .Times(1);
   } else {
-    EXPECT_CALL(session_, MaybeSendRstStreamFrame(_, QUIC_STREAM_NO_ERROR, _))
+    EXPECT_CALL(
+        session_,
+        MaybeSendRstStreamFrame(
+            _, QuicResetStreamError::FromInternal(QUIC_STREAM_NO_ERROR), _))
         .Times(1);
   }
   QuicStreamPeer::SetFinSent(stream_);
@@ -71,9 +76,10 @@
   EXPECT_CALL(session_,
               MaybeSendRstStreamFrame(
                   _,
-                  VersionHasIetfQuicFrames(session_.transport_version())
-                      ? QUIC_STREAM_CANCELLED
-                      : QUIC_RST_ACKNOWLEDGEMENT,
+                  QuicResetStreamError::FromInternal(
+                      VersionHasIetfQuicFrames(session_.transport_version())
+                          ? QUIC_STREAM_CANCELLED
+                          : QUIC_RST_ACKNOWLEDGEMENT),
                   _))
       .Times(1);
   QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream_->id(),
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc
index 5ac34fc..4dcfe01 100644
--- a/quic/core/http/quic_spdy_session.cc
+++ b/quic/core/http/quic_spdy_session.cc
@@ -1413,7 +1413,9 @@
     default:
       break;
   }
-  MaybeSendStopSendingFrame(pending->id(), QUIC_STREAM_STREAM_CREATION_ERROR);
+  MaybeSendStopSendingFrame(
+      pending->id(),
+      QuicResetStreamError::FromInternal(QUIC_STREAM_STREAM_CREATION_ERROR));
   pending->StopReading();
   return nullptr;
 }
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
index 2ff3b3a..f4fa3ee 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -726,11 +726,11 @@
                 << "Received QUIC_STREAM_NO_ERROR, not discarding response";
   set_rst_received(true);
   MaybeIncreaseHighestReceivedOffset(frame.byte_offset);
-  set_stream_error(frame.error_code);
+  set_stream_error(frame.error());
   CloseWriteSide();
 }
 
-void QuicSpdyStream::Reset(QuicRstStreamErrorCode error) {
+void QuicSpdyStream::ResetWithError(QuicResetStreamError error) {
   if (VersionUsesHttp3(transport_version()) && !fin_received() &&
       spdy_session_->qpack_decoder() && web_transport_data_ == nullptr) {
     QUIC_CODE_COUNT_N(quic_abort_qpack_on_stream_reset, 2, 2);
@@ -741,7 +741,7 @@
     }
   }
 
-  QuicStream::Reset(error);
+  QuicStream::ResetWithError(error);
 }
 
 void QuicSpdyStream::OnDataAvailable() {
diff --git a/quic/core/http/quic_spdy_stream.h b/quic/core/http/quic_spdy_stream.h
index 7a019e0..e3e3d6a 100644
--- a/quic/core/http/quic_spdy_stream.h
+++ b/quic/core/http/quic_spdy_stream.h
@@ -111,7 +111,7 @@
   // QUIC_STREAM_NO_ERROR.
   void OnStreamReset(const QuicRstStreamFrame& frame) override;
 
-  void Reset(QuicRstStreamErrorCode error) override;
+  void ResetWithError(QuicResetStreamError error) override;
 
   // Called by the sequencer when new data is available. Decodes the data and
   // calls OnBodyAvailable() to pass to the upper layer.
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc
index 40ee645..63a77ce 100644
--- a/quic/core/http/quic_spdy_stream_test.cc
+++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -510,8 +510,11 @@
     stream_->OnStreamHeadersPriority(
         spdy::SpdyStreamPrecedence(kV3HighestPriority));
 
-    EXPECT_CALL(*session_, MaybeSendRstStreamFrame(stream_->id(),
-                                                   QUIC_HEADERS_TOO_LARGE, 0));
+    EXPECT_CALL(
+        *session_,
+        MaybeSendRstStreamFrame(
+            stream_->id(),
+            QuicResetStreamError::FromInternal(QUIC_HEADERS_TOO_LARGE), 0));
     stream_->OnStreamHeaderList(false, 1 << 20, headers);
 
     EXPECT_THAT(stream_->stream_error(), IsStreamError(QUIC_HEADERS_TOO_LARGE));
@@ -526,10 +529,14 @@
 
   QuicStreamFrame frame(stream_->id(), false, 0, headers);
 
-  EXPECT_CALL(*session_,
-              MaybeSendStopSendingFrame(stream_->id(), QUIC_HEADERS_TOO_LARGE));
-  EXPECT_CALL(*session_, MaybeSendRstStreamFrame(stream_->id(),
-                                                 QUIC_HEADERS_TOO_LARGE, 0));
+  EXPECT_CALL(*session_, MaybeSendStopSendingFrame(
+                             stream_->id(), QuicResetStreamError::FromInternal(
+                                                QUIC_HEADERS_TOO_LARGE)));
+  EXPECT_CALL(
+      *session_,
+      MaybeSendRstStreamFrame(
+          stream_->id(),
+          QuicResetStreamError::FromInternal(QUIC_HEADERS_TOO_LARGE), 0));
 
   auto qpack_decoder_stream =
       QuicSpdySessionPeer::GetQpackDecoderSendStream(session_.get());
@@ -2462,10 +2469,14 @@
                          /* offset = */ 1, _, _, _));
 
   // Reset stream by this endpoint, for example, due to stream cancellation.
-  EXPECT_CALL(*session_,
-              MaybeSendStopSendingFrame(stream_->id(), QUIC_STREAM_CANCELLED));
-  EXPECT_CALL(*session_,
-              MaybeSendRstStreamFrame(stream_->id(), QUIC_STREAM_CANCELLED, _));
+  EXPECT_CALL(*session_, MaybeSendStopSendingFrame(
+                             stream_->id(), QuicResetStreamError::FromInternal(
+                                                QUIC_STREAM_CANCELLED)));
+  EXPECT_CALL(
+      *session_,
+      MaybeSendRstStreamFrame(
+          stream_->id(),
+          QuicResetStreamError::FromInternal(QUIC_STREAM_CANCELLED), _));
   stream_->Reset(QUIC_STREAM_CANCELLED);
 
   // Deliver dynamic table entry to decoder.
@@ -2908,10 +2919,14 @@
   EXPECT_CALL(*session_,
               WritevData(qpack_decoder_stream->id(), /* write_length = */ 1,
                          /* offset = */ 1, _, _, _));
-  EXPECT_CALL(*session_,
-              MaybeSendStopSendingFrame(stream_->id(), QUIC_STREAM_CANCELLED));
-  EXPECT_CALL(*session_,
-              MaybeSendRstStreamFrame(stream_->id(), QUIC_STREAM_CANCELLED, _));
+  EXPECT_CALL(*session_, MaybeSendStopSendingFrame(
+                             stream_->id(), QuicResetStreamError::FromInternal(
+                                                QUIC_STREAM_CANCELLED)));
+  EXPECT_CALL(
+      *session_,
+      MaybeSendRstStreamFrame(
+          stream_->id(),
+          QuicResetStreamError::FromInternal(QUIC_STREAM_CANCELLED), _));
 
   stream_->Reset(QUIC_STREAM_CANCELLED);
 }
@@ -3003,8 +3018,11 @@
     EXPECT_TRUE(stream_->read_side_closed());
     EXPECT_FALSE(stream_->write_side_closed());
   } else {
-    EXPECT_CALL(*session_, MaybeSendRstStreamFrame(
-                               stream_->id(), QUIC_RST_ACKNOWLEDGEMENT, _));
+    EXPECT_CALL(
+        *session_,
+        MaybeSendRstStreamFrame(
+            stream_->id(),
+            QuicResetStreamError::FromInternal(QUIC_RST_ACKNOWLEDGEMENT), _));
     EXPECT_QUIC_BUG(
         stream_->OnStreamReset(rst_frame2),
         "The stream should've already sent RST in response to STOP_SENDING");
diff --git a/quic/core/qpack/qpack_send_stream.cc b/quic/core/qpack/qpack_send_stream.cc
index 0d5d8af..626d063 100644
--- a/quic/core/qpack/qpack_send_stream.cc
+++ b/quic/core/qpack/qpack_send_stream.cc
@@ -21,7 +21,7 @@
       << "OnStreamReset() called for write unidirectional stream.";
 }
 
-bool QpackSendStream::OnStopSending(QuicRstStreamErrorCode /* code */) {
+bool QpackSendStream::OnStopSending(QuicResetStreamError /* code */) {
   stream_delegate()->OnStreamError(
       QUIC_HTTP_CLOSED_CRITICAL_STREAM,
       "STOP_SENDING received for QPACK send stream");
diff --git a/quic/core/qpack/qpack_send_stream.h b/quic/core/qpack/qpack_send_stream.h
index fad159c..086ad63 100644
--- a/quic/core/qpack/qpack_send_stream.h
+++ b/quic/core/qpack/qpack_send_stream.h
@@ -33,7 +33,7 @@
   // Overriding QuicStream::OnStopSending() to make sure QPACK stream is never
   // closed before connection.
   void OnStreamReset(const QuicRstStreamFrame& frame) override;
-  bool OnStopSending(QuicRstStreamErrorCode code) override;
+  bool OnStopSending(QuicResetStreamError code) override;
 
   // The send QPACK stream is write unidirectional, so this method
   // should never be called.
diff --git a/quic/core/qpack/qpack_send_stream_test.cc b/quic/core/qpack/qpack_send_stream_test.cc
index df4668b..5a98951 100644
--- a/quic/core/qpack/qpack_send_stream_test.cc
+++ b/quic/core/qpack/qpack_send_stream_test.cc
@@ -119,7 +119,8 @@
 TEST_P(QpackSendStreamTest, StopSendingQpackStream) {
   EXPECT_CALL(*connection_,
               CloseConnection(QUIC_HTTP_CLOSED_CRITICAL_STREAM, _, _));
-  qpack_send_stream_->OnStopSending(QUIC_STREAM_CANCELLED);
+  qpack_send_stream_->OnStopSending(
+      QuicResetStreamError::FromInternal(QUIC_STREAM_CANCELLED));
 }
 
 TEST_P(QpackSendStreamTest, ReceiveDataOnSendStream) {
diff --git a/quic/core/quic_control_frame_manager.cc b/quic/core/quic_control_frame_manager.cc
index afcdabc..39e768c 100644
--- a/quic/core/quic_control_frame_manager.cc
+++ b/quic/core/quic_control_frame_manager.cc
@@ -59,7 +59,7 @@
 }
 
 void QuicControlFrameManager::WriteOrBufferRstStream(
-    QuicStreamId id, QuicRstStreamErrorCode error,
+    QuicStreamId id, QuicResetStreamError error,
     QuicStreamOffset bytes_written) {
   QUIC_DVLOG(1) << "Writing RST_STREAM_FRAME";
   WriteOrBufferQuicFrame((QuicFrame(new QuicRstStreamFrame(
@@ -104,10 +104,10 @@
 }
 
 void QuicControlFrameManager::WriteOrBufferStopSending(
-    QuicRstStreamErrorCode code, QuicStreamId stream_id) {
+    QuicResetStreamError error, QuicStreamId stream_id) {
   QUIC_DVLOG(1) << "Writing STOP_SENDING_FRAME";
   WriteOrBufferQuicFrame(QuicFrame(
-      new QuicStopSendingFrame(++last_control_frame_id_, stream_id, code)));
+      new QuicStopSendingFrame(++last_control_frame_id_, stream_id, error)));
 }
 
 void QuicControlFrameManager::WriteOrBufferHandshakeDone() {
diff --git a/quic/core/quic_control_frame_manager.h b/quic/core/quic_control_frame_manager.h
index 962733a..e83fc3b 100644
--- a/quic/core/quic_control_frame_manager.h
+++ b/quic/core/quic_control_frame_manager.h
@@ -11,6 +11,7 @@
 #include "absl/container/flat_hash_map.h"
 #include "quic/core/frames/quic_frame.h"
 #include "quic/core/quic_connection_id.h"
+#include "quic/core/quic_error_codes.h"
 #include "quic/core/quic_types.h"
 #include "common/quiche_circular_deque.h"
 #include "common/quiche_linked_hash_map.h"
@@ -53,8 +54,7 @@
 
   // Tries to send a WINDOW_UPDATE_FRAME. Buffers the frame if it cannot be sent
   // immediately.
-  void WriteOrBufferRstStream(QuicControlFrameId id,
-                              QuicRstStreamErrorCode error,
+  void WriteOrBufferRstStream(QuicControlFrameId id, QuicResetStreamError error,
                               QuicStreamOffset bytes_written);
 
   // Tries to send a GOAWAY_FRAME. Buffers the frame if it cannot be sent
@@ -81,7 +81,7 @@
 
   // Tries to send an IETF-QUIC STOP_SENDING frame. The frame is buffered if it
   // can not be sent immediately.
-  void WriteOrBufferStopSending(QuicRstStreamErrorCode code,
+  void WriteOrBufferStopSending(QuicResetStreamError error,
                                 QuicStreamId stream_id);
 
   // Tries to send an HANDSHAKE_DONE frame. The frame is buffered if it can not
@@ -96,8 +96,7 @@
   // Tries to send a NEW_CONNECTION_ID frame. The frame is buffered if it cannot
   // be sent immediately.
   void WriteOrBufferNewConnectionId(
-      const QuicConnectionId& connection_id,
-      uint64_t sequence_number,
+      const QuicConnectionId& connection_id, uint64_t sequence_number,
       uint64_t retire_prior_to,
       const StatelessResetToken& stateless_reset_token);
 
diff --git a/quic/core/quic_control_frame_manager_test.cc b/quic/core/quic_control_frame_manager_test.cc
index 9d6b434..6d20811 100644
--- a/quic/core/quic_control_frame_manager_test.cc
+++ b/quic/core/quic_control_frame_manager_test.cc
@@ -66,12 +66,16 @@
     EXPECT_FALSE(manager_->WillingToWrite());
 
     EXPECT_CALL(*session_, WriteControlFrame(_, _)).WillOnce(Return(false));
-    manager_->WriteOrBufferRstStream(kTestStreamId, QUIC_STREAM_CANCELLED, 0);
+    manager_->WriteOrBufferRstStream(
+        kTestStreamId,
+        QuicResetStreamError::FromInternal(QUIC_STREAM_CANCELLED), 0);
     manager_->WriteOrBufferGoAway(QUIC_PEER_GOING_AWAY, kTestStreamId,
                                   "Going away.");
     manager_->WriteOrBufferWindowUpdate(kTestStreamId, 100);
     manager_->WriteOrBufferBlocked(kTestStreamId);
-    manager_->WriteOrBufferStopSending(kTestStopSendingCode, kTestStreamId);
+    manager_->WriteOrBufferStopSending(
+        QuicResetStreamError::FromInternal(kTestStopSendingCode),
+        kTestStreamId);
     number_of_frames_ = 5u;
     EXPECT_EQ(number_of_frames_,
               QuicControlFrameManagerPeer::QueueSize(manager_.get()));
@@ -341,14 +345,18 @@
   // Write 995 control frames.
   EXPECT_CALL(*session_, WriteControlFrame(_, _)).WillOnce(Return(false));
   for (size_t i = 0; i < 995; ++i) {
-    manager_->WriteOrBufferRstStream(kTestStreamId, QUIC_STREAM_CANCELLED, 0);
+    manager_->WriteOrBufferRstStream(
+        kTestStreamId,
+        QuicResetStreamError::FromInternal(QUIC_STREAM_CANCELLED), 0);
   }
   // Verify write one more control frame causes connection close.
   EXPECT_CALL(
       *connection_,
       CloseConnection(QUIC_TOO_MANY_BUFFERED_CONTROL_FRAMES, _,
                       ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET));
-  manager_->WriteOrBufferRstStream(kTestStreamId, QUIC_STREAM_CANCELLED, 0);
+  manager_->WriteOrBufferRstStream(
+      kTestStreamId, QuicResetStreamError::FromInternal(QUIC_STREAM_CANCELLED),
+      0);
 }
 
 }  // namespace
diff --git a/quic/core/quic_error_codes.cc b/quic/core/quic_error_codes.cc
index c3fcb88..afd0bfb 100644
--- a/quic/core/quic_error_codes.cc
+++ b/quic/core/quic_error_codes.cc
@@ -939,6 +939,19 @@
   return QUIC_STREAM_UNKNOWN_APPLICATION_ERROR_CODE;
 }
 
+// static
+QuicResetStreamError QuicResetStreamError::FromInternal(
+    QuicRstStreamErrorCode code) {
+  return QuicResetStreamError(
+      code, RstStreamErrorCodeToIetfResetStreamErrorCode(code));
+}
+
+// static
+QuicResetStreamError QuicResetStreamError::FromIetf(uint64_t code) {
+  return QuicResetStreamError(
+      IetfResetStreamErrorCodeToRstStreamErrorCode(code), code);
+}
+
 #undef RETURN_STRING_LITERAL  // undef for jumbo builds
 
 }  // namespace quic
diff --git a/quic/core/quic_error_codes.h b/quic/core/quic_error_codes.h
index 53a8810..f43ad0a 100644
--- a/quic/core/quic_error_codes.h
+++ b/quic/core/quic_error_codes.h
@@ -609,6 +609,45 @@
                   static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()),
               "QuicErrorCode exceeds four octets");
 
+// Represents a reason for resetting a stream in both gQUIC and IETF error code
+// space.  Both error codes have to be present.
+class QUIC_EXPORT_PRIVATE QuicResetStreamError {
+ public:
+  // Constructs a QuicResetStreamError from QuicRstStreamErrorCode; the IETF
+  // error code is inferred.
+  static QuicResetStreamError FromInternal(QuicRstStreamErrorCode code);
+  // Constructs a QuicResetStreamError from an IETF error code; the internal
+  // error code is inferred.
+  static QuicResetStreamError FromIetf(uint64_t code);
+  // Constructs a QuicResetStreamError with no error.
+  static QuicResetStreamError NoError() {
+    return FromInternal(QUIC_STREAM_NO_ERROR);
+  }
+
+  QuicResetStreamError(QuicRstStreamErrorCode internal_code,
+                       uint64_t ietf_application_code)
+      : internal_code_(internal_code),
+        ietf_application_code_(ietf_application_code) {}
+
+  QuicRstStreamErrorCode internal_code() const { return internal_code_; }
+  uint64_t ietf_application_code() const { return ietf_application_code_; }
+
+  bool operator==(const QuicResetStreamError& other) const {
+    return internal_code() == other.internal_code() &&
+           ietf_application_code() == other.ietf_application_code();
+  }
+
+  // Returns true if the object holds no error.
+  bool ok() const { return internal_code() == QUIC_STREAM_NO_ERROR; }
+
+ private:
+  // Error code used in gQUIC.  Even when IETF QUIC is in use, this needs to be
+  // populated as we use those internally.
+  QuicRstStreamErrorCode internal_code_;
+  // Application error code used in IETF QUIC.
+  uint64_t ietf_application_code_;
+};
+
 // Convert TLS alert code to QuicErrorCode.
 QUIC_EXPORT_PRIVATE QuicErrorCode TlsAlertToQuicErrorCode(uint8_t desc);
 
@@ -645,8 +684,7 @@
     QuicIetfTransportErrorCodes c);
 
 QUIC_EXPORT_PRIVATE std::ostream& operator<<(
-    std::ostream& os,
-    const QuicIetfTransportErrorCodes& c);
+    std::ostream& os, const QuicIetfTransportErrorCodes& c);
 
 // A transport error code (if is_transport_close is true) or application error
 // code (if is_transport_close is false) to be used in CONNECTION_CLOSE frames.
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index 5945ed6..c4af7b4 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -107,6 +107,8 @@
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_conservative_cwnd_and_pacing_gains, false)
 // If true, validate that peer owns the new address once the server detects peer migration or is probed from that address, and also apply anti-amplification limit while sending to that address.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_server_reverse_validate_new_path3, true)
+// When receiving STOP_SENDING, send a RESET_STREAM with a matching error code.
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_match_ietf_reset_code, false)
 // When the STMP connection option is sent by the client, timestamps in the QUIC ACK frame are sent and processed.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_send_timestamps, false)
 // When true, QuicDispatcher will silently drop QUIC packets that have invalid flags.
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index 60a2b45..e720221 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -276,7 +276,7 @@
     return;
   }
 
-  stream->OnStopSending(frame.error_code);
+  stream->OnStopSending(frame.error());
 }
 
 void QuicSession::OnPacketDecrypted(EncryptionLevel level) {
@@ -854,12 +854,12 @@
   }
 
   QuicConnection::ScopedPacketFlusher flusher(connection());
-  MaybeSendStopSendingFrame(id, error);
-  MaybeSendRstStreamFrame(id, error, 0);
+  MaybeSendStopSendingFrame(id, QuicResetStreamError::FromInternal(error));
+  MaybeSendRstStreamFrame(id, QuicResetStreamError::FromInternal(error), 0);
 }
 
 void QuicSession::MaybeSendRstStreamFrame(QuicStreamId id,
-                                          QuicRstStreamErrorCode error,
+                                          QuicResetStreamError error,
                                           QuicStreamOffset bytes_written) {
   if (!connection()->connected()) {
     return;
@@ -870,11 +870,11 @@
     control_frame_manager_.WriteOrBufferRstStream(id, error, bytes_written);
   }
 
-  connection_->OnStreamReset(id, error);
+  connection_->OnStreamReset(id, error.internal_code());
 }
 
 void QuicSession::MaybeSendStopSendingFrame(QuicStreamId id,
-                                            QuicRstStreamErrorCode error) {
+                                            QuicResetStreamError error) {
   if (!connection()->connected()) {
     return;
   }
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h
index 431824b..d9de347 100644
--- a/quic/core/quic_session.h
+++ b/quic/core/quic_session.h
@@ -580,12 +580,12 @@
   // Does actual work of sending RESET_STREAM, if the stream type allows.
   // Also informs the connection so that pending stream frames can be flushed.
   virtual void MaybeSendRstStreamFrame(QuicStreamId id,
-                                       QuicRstStreamErrorCode error,
+                                       QuicResetStreamError error,
                                        QuicStreamOffset bytes_written);
 
   // Sends a STOP_SENDING frame if the stream type allows.
   virtual void MaybeSendStopSendingFrame(QuicStreamId id,
-                                         QuicRstStreamErrorCode error);
+                                         QuicResetStreamError error);
 
   // Returns the encryption level to send application data.
   EncryptionLevel GetEncryptionLevelToSendApplicationData() const;
diff --git a/quic/core/quic_stream.cc b/quic/core/quic_stream.cc
index e87bc01..4c83fe8 100644
--- a/quic/core/quic_stream.cc
+++ b/quic/core/quic_stream.cc
@@ -142,7 +142,7 @@
   connection_flow_controller_->AddBytesConsumed(bytes);
 }
 
-void PendingStream::Reset(QuicRstStreamErrorCode /*error*/) {
+void PendingStream::ResetWithError(QuicResetStreamError /*error*/) {
   // Currently PendingStream is only read-unidirectional. It shouldn't send
   // Reset.
   QUIC_NOTREACHED();
@@ -330,7 +330,7 @@
       stream_delegate_(session),
       precedence_(CalculateDefaultPriority(session)),
       stream_bytes_read_(stream_bytes_read),
-      stream_error_(QUIC_STREAM_NO_ERROR),
+      stream_error_(QuicResetStreamError::NoError()),
       connection_error_(QUIC_NO_ERROR),
       read_side_closed_(false),
       write_side_closed_(false),
@@ -472,7 +472,7 @@
   sequencer_.OnStreamFrame(frame);
 }
 
-bool QuicStream::OnStopSending(QuicRstStreamErrorCode code) {
+bool QuicStream::OnStopSending(QuicResetStreamError error) {
   // Do not reset the stream if all data has been sent and acknowledged.
   if (write_side_closed() && !IsWaitingForAcks()) {
     QUIC_DVLOG(1) << ENDPOINT
@@ -490,8 +490,14 @@
     return false;
   }
 
-  stream_error_ = code;
-  MaybeSendRstStream(code);
+  stream_error_ = error;
+  if (GetQuicReloadableFlag(quic_match_ietf_reset_code)) {
+    QUIC_RELOADABLE_FLAG_COUNT(quic_match_ietf_reset_code);
+    MaybeSendRstStream(error);
+  } else {
+    MaybeSendRstStream(
+        QuicResetStreamError::FromInternal(error.internal_code()));
+  }
   return true;
 }
 
@@ -536,7 +542,7 @@
     return;
   }
 
-  stream_error_ = frame.error_code;
+  stream_error_ = frame.error();
   // Google QUIC closes both sides of the stream in response to a
   // RESET_STREAM, IETF QUIC closes only the read side.
   if (!VersionHasIetfQuicFrames(transport_version())) {
@@ -551,7 +557,8 @@
     return;
   }
   if (error != QUIC_NO_ERROR) {
-    stream_error_ = QUIC_STREAM_CONNECTION_ERROR;
+    stream_error_ =
+        QuicResetStreamError::FromInternal(QUIC_STREAM_CONNECTION_ERROR);
     connection_error_ = error;
   }
 
@@ -576,6 +583,10 @@
 }
 
 void QuicStream::Reset(QuicRstStreamErrorCode error) {
+  ResetWithError(QuicResetStreamError::FromInternal(error));
+}
+
+void QuicStream::ResetWithError(QuicResetStreamError error) {
   stream_error_ = error;
   QuicConnection::ScopedPacketFlusher flusher(session()->connection());
   MaybeSendStopSending(error);
@@ -818,12 +829,12 @@
   }
 }
 
-void QuicStream::MaybeSendStopSending(QuicRstStreamErrorCode error) {
+void QuicStream::MaybeSendStopSending(QuicResetStreamError error) {
   if (stop_sending_sent_) {
     return;
   }
 
-  if (!session()->version().UsesHttp3() && error != QUIC_STREAM_NO_ERROR) {
+  if (!session()->version().UsesHttp3() && !error.ok()) {
     // In gQUIC, RST with error closes both read and write side.
     return;
   }
@@ -831,21 +842,21 @@
   if (session()->version().UsesHttp3()) {
     session()->MaybeSendStopSendingFrame(id(), error);
   } else {
-    QUICHE_DCHECK_EQ(QUIC_STREAM_NO_ERROR, error);
-    session()->MaybeSendRstStreamFrame(id(), QUIC_STREAM_NO_ERROR,
+    QUICHE_DCHECK_EQ(QUIC_STREAM_NO_ERROR, error.internal_code());
+    session()->MaybeSendRstStreamFrame(id(), QuicResetStreamError::NoError(),
                                        stream_bytes_written());
   }
   stop_sending_sent_ = true;
   CloseReadSide();
 }
 
-void QuicStream::MaybeSendRstStream(QuicRstStreamErrorCode error) {
+void QuicStream::MaybeSendRstStream(QuicResetStreamError error) {
   if (rst_sent_) {
     return;
   }
 
   if (!session()->version().UsesHttp3()) {
-    QUIC_BUG_IF(quic_bug_12570_5, error == QUIC_STREAM_NO_ERROR);
+    QUIC_BUG_IF(quic_bug_12570_5, error.ok());
     stop_sending_sent_ = true;
     CloseReadSide();
   }
@@ -1150,7 +1161,7 @@
 }
 
 bool QuicStream::IsWaitingForAcks() const {
-  return (!rst_sent_ || stream_error_ == QUIC_STREAM_NO_ERROR) &&
+  return (!rst_sent_ || stream_error_.ok()) &&
          (send_buffer_.stream_bytes_outstanding() || fin_outstanding_);
 }
 
diff --git a/quic/core/quic_stream.h b/quic/core/quic_stream.h
index 10ef313..64e79bd 100644
--- a/quic/core/quic_stream.h
+++ b/quic/core/quic_stream.h
@@ -25,6 +25,7 @@
 #include "absl/strings/string_view.h"
 #include "absl/types/optional.h"
 #include "absl/types/span.h"
+#include "quic/core/quic_error_codes.h"
 #include "quic/core/quic_flow_controller.h"
 #include "quic/core/quic_packets.h"
 #include "quic/core/quic_stream_send_buffer.h"
@@ -59,7 +60,7 @@
   void OnDataAvailable() override;
   void OnFinRead() override;
   void AddBytesConsumed(QuicByteCount bytes) override;
-  void Reset(QuicRstStreamErrorCode error) override;
+  void ResetWithError(QuicResetStreamError error) override;
   void OnUnrecoverableError(QuicErrorCode error,
                             const std::string& details) override;
   void OnUnrecoverableError(QuicErrorCode error,
@@ -156,7 +157,11 @@
 
   // Called by the subclass or the sequencer to reset the stream from this
   // end.
-  void Reset(QuicRstStreamErrorCode error) override;
+  void ResetWithError(QuicResetStreamError error) override;
+  // Convenience wrapper for the method above.
+  // TODO(b/200606367): switch all calls to using QuicResetStreamError
+  // interface.
+  void Reset(QuicRstStreamErrorCode error);
 
   // Called by the subclass or the sequencer to close the entire connection from
   // this end.
@@ -204,7 +209,9 @@
   // Number of bytes available to read.
   QuicByteCount ReadableBytes() const;
 
-  QuicRstStreamErrorCode stream_error() const { return stream_error_; }
+  QuicRstStreamErrorCode stream_error() const {
+    return stream_error_.internal_code();
+  }
   QuicErrorCode connection_error() const { return connection_error_; }
 
   bool reading_stopped() const {
@@ -353,7 +360,7 @@
 
   // Handle received StopSending frame. Returns true if the processing finishes
   // gracefully.
-  virtual bool OnStopSending(QuicRstStreamErrorCode code);
+  virtual bool OnStopSending(QuicResetStreamError error);
 
   // Returns true if the stream is static.
   bool is_static() const { return is_static_; }
@@ -415,10 +422,18 @@
   void SetFinSent();
 
   // Send STOP_SENDING if it hasn't been sent yet.
-  void MaybeSendStopSending(QuicRstStreamErrorCode error);
+  void MaybeSendStopSending(QuicResetStreamError error);
 
   // Send RESET_STREAM if it hasn't been sent yet.
-  void MaybeSendRstStream(QuicRstStreamErrorCode error);
+  void MaybeSendRstStream(QuicResetStreamError error);
+
+  // Convenience warppers for two methods above.
+  void MaybeSendRstStream(QuicRstStreamErrorCode error) {
+    MaybeSendRstStream(QuicResetStreamError::FromInternal(error));
+  }
+  void MaybeSendStopSending(QuicRstStreamErrorCode error) {
+    MaybeSendStopSending(QuicResetStreamError::FromInternal(error));
+  }
 
   // Close the write side of the socket.  Further writes will fail.
   // Can be called by the subclass or internally.
@@ -426,7 +441,7 @@
   virtual void CloseWriteSide();
 
   void set_rst_received(bool rst_received) { rst_received_ = rst_received; }
-  void set_stream_error(QuicRstStreamErrorCode error) { stream_error_ = error; }
+  void set_stream_error(QuicResetStreamError error) { stream_error_ = error; }
 
   StreamDelegateInterface* stream_delegate() { return stream_delegate_; }
 
@@ -492,7 +507,7 @@
 
   // Stream error code received from a RstStreamFrame or error code sent by the
   // visitor or sequencer in the RstStreamFrame.
-  QuicRstStreamErrorCode stream_error_;
+  QuicResetStreamError stream_error_;
   // Connection error code due to which the stream was closed. |stream_error_|
   // is set to |QUIC_STREAM_CONNECTION_ERROR| when this happens and consumers
   // should check |connection_error_|.
diff --git a/quic/core/quic_stream_sequencer.cc b/quic/core/quic_stream_sequencer.cc
index 88a51bd..1dbe5df 100644
--- a/quic/core/quic_stream_sequencer.cc
+++ b/quic/core/quic_stream_sequencer.cc
@@ -236,7 +236,8 @@
         << "Invalid argument to MarkConsumed."
         << " expect to consume: " << num_bytes_consumed
         << ", but not enough bytes available. " << DebugString();
-    stream_->Reset(QUIC_ERROR_PROCESSING_STREAM);
+    stream_->ResetWithError(
+        QuicResetStreamError::FromInternal(QUIC_ERROR_PROCESSING_STREAM));
     return;
   }
   stream_->AddBytesConsumed(num_bytes_consumed);
diff --git a/quic/core/quic_stream_sequencer.h b/quic/core/quic_stream_sequencer.h
index b7ea49d..fe96f25 100644
--- a/quic/core/quic_stream_sequencer.h
+++ b/quic/core/quic_stream_sequencer.h
@@ -37,7 +37,7 @@
     virtual void AddBytesConsumed(QuicByteCount bytes) = 0;
     // Called when an error has occurred which should result in the stream
     // being reset.
-    virtual void Reset(QuicRstStreamErrorCode error) = 0;
+    virtual void ResetWithError(QuicResetStreamError error) = 0;
     // Called when an error has occurred which should result in the connection
     // being closed.
     virtual void OnUnrecoverableError(QuicErrorCode error,
diff --git a/quic/core/quic_stream_sequencer_test.cc b/quic/core/quic_stream_sequencer_test.cc
index b097bc5..0a15e96 100644
--- a/quic/core/quic_stream_sequencer_test.cc
+++ b/quic/core/quic_stream_sequencer_test.cc
@@ -43,7 +43,7 @@
                QuicIetfTransportErrorCodes ietf_error,
                const std::string& details),
               (override));
-  MOCK_METHOD(void, Reset, (QuicRstStreamErrorCode error), (override));
+  MOCK_METHOD(void, ResetWithError, (QuicResetStreamError error), (override));
   MOCK_METHOD(void, AddBytesConsumed, (QuicByteCount bytes), (override));
 
   QuicStreamId id() const override { return 1; }
@@ -566,7 +566,8 @@
 
   // Now, attempt to mark consumed more data than was readable and expect the
   // stream to be closed.
-  EXPECT_CALL(stream_, Reset(QUIC_ERROR_PROCESSING_STREAM));
+  EXPECT_CALL(stream_, ResetWithError(QuicResetStreamError::FromInternal(
+                           QUIC_ERROR_PROCESSING_STREAM)));
   EXPECT_QUIC_BUG(sequencer_->MarkConsumed(4),
                   "Invalid argument to MarkConsumed."
                   " expect to consume: 4, but not enough bytes available.");
diff --git a/quic/core/quic_stream_test.cc b/quic/core/quic_stream_test.cc
index 9d37128..f412870 100644
--- a/quic/core/quic_stream_test.cc
+++ b/quic/core/quic_stream_test.cc
@@ -129,12 +129,9 @@
   }
 
   QuicConsumedData CloseStreamOnWriteError(
-      QuicStreamId id,
-      QuicByteCount /*write_length*/,
-      QuicStreamOffset /*offset*/,
-      StreamSendingState /*state*/,
-      TransmissionType /*type*/,
-      absl::optional<EncryptionLevel> /*level*/) {
+      QuicStreamId id, QuicByteCount /*write_length*/,
+      QuicStreamOffset /*offset*/, StreamSendingState /*state*/,
+      TransmissionType /*type*/, absl::optional<EncryptionLevel> /*level*/) {
     session_->ResetStream(id, QUIC_STREAM_CANCELLED);
     return QuicConsumedData(1, false);
   }
@@ -168,8 +165,7 @@
                                                   1);
 };
 
-INSTANTIATE_TEST_SUITE_P(QuicStreamTests,
-                         QuicStreamTest,
+INSTANTIATE_TEST_SUITE_P(QuicStreamTests, QuicStreamTest,
                          ::testing::ValuesIn(AllSupportedVersions()),
                          ::testing::PrintToStringParamName());
 
@@ -998,8 +994,11 @@
   QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream_->id(),
                                QUIC_STREAM_CANCELLED, 9);
 
-  EXPECT_CALL(*session_, MaybeSendRstStreamFrame(stream_->id(),
-                                                 QUIC_RST_ACKNOWLEDGEMENT, 9));
+  EXPECT_CALL(
+      *session_,
+      MaybeSendRstStreamFrame(
+          stream_->id(),
+          QuicResetStreamError::FromInternal(QUIC_RST_ACKNOWLEDGEMENT), 9));
   stream_->OnStreamReset(rst_frame);
   EXPECT_EQ(1u, QuicStreamPeer::SendBuffer(stream_).size());
   // Stream stops waiting for acks as it does not finish sending and rst is
@@ -1041,8 +1040,11 @@
   stream_->WriteOrBufferData(kData1, false, nullptr);
   EXPECT_TRUE(stream_->IsWaitingForAcks());
   EXPECT_TRUE(session_->HasUnackedStreamData());
-  EXPECT_CALL(*session_, MaybeSendRstStreamFrame(stream_->id(),
-                                                 QUIC_RST_ACKNOWLEDGEMENT, 9));
+  EXPECT_CALL(
+      *session_,
+      MaybeSendRstStreamFrame(
+          stream_->id(),
+          QuicResetStreamError::FromInternal(QUIC_RST_ACKNOWLEDGEMENT), 9));
   QuicConnectionPeer::SetConnectionClose(connection_);
   stream_->OnConnectionClosed(QUIC_INTERNAL_ERROR,
                               ConnectionCloseSource::FROM_SELF);
@@ -1564,10 +1566,14 @@
   // Verify stream gets reset because TTL expires.
   if (session_->version().UsesHttp3()) {
     EXPECT_CALL(*session_,
-                MaybeSendStopSendingFrame(_, QUIC_STREAM_TTL_EXPIRED))
+                MaybeSendStopSendingFrame(_, QuicResetStreamError::FromInternal(
+                                                 QUIC_STREAM_TTL_EXPIRED)))
         .Times(1);
   }
-  EXPECT_CALL(*session_, MaybeSendRstStreamFrame(_, QUIC_STREAM_TTL_EXPIRED, _))
+  EXPECT_CALL(
+      *session_,
+      MaybeSendRstStreamFrame(
+          _, QuicResetStreamError::FromInternal(QUIC_STREAM_TTL_EXPIRED), _))
       .Times(1);
   stream_->OnCanWrite();
 }
@@ -1588,10 +1594,14 @@
   // Verify stream gets reset because TTL expires.
   if (session_->version().UsesHttp3()) {
     EXPECT_CALL(*session_,
-                MaybeSendStopSendingFrame(_, QUIC_STREAM_TTL_EXPIRED))
+                MaybeSendStopSendingFrame(_, QuicResetStreamError::FromInternal(
+                                                 QUIC_STREAM_TTL_EXPIRED)))
         .Times(1);
   }
-  EXPECT_CALL(*session_, MaybeSendRstStreamFrame(_, QUIC_STREAM_TTL_EXPIRED, _))
+  EXPECT_CALL(
+      *session_,
+      MaybeSendRstStreamFrame(
+          _, QuicResetStreamError::FromInternal(QUIC_STREAM_TTL_EXPIRED), _))
       .Times(1);
   stream_->RetransmitStreamData(0, 100, false, PTO_RETRANSMISSION);
 }
@@ -1662,6 +1672,15 @@
   stream_->OnStreamFrame(empty_stream_frame);
 }
 
+TEST_P(QuicStreamTest, SendRstWithCustomIetfCode) {
+  Initialize();
+  QuicResetStreamError error(QUIC_STREAM_CANCELLED, 0x1234abcd);
+  EXPECT_CALL(*session_, MaybeSendRstStreamFrame(kTestStreamId, error, _))
+      .Times(1);
+  stream_->ResetWithError(error);
+  EXPECT_TRUE(rst_sent());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/tls_chlo_extractor.h b/quic/core/tls_chlo_extractor.h
index c6d0455..4b1cf31 100644
--- a/quic/core/tls_chlo_extractor.h
+++ b/quic/core/tls_chlo_extractor.h
@@ -177,7 +177,7 @@
   void OnDataAvailable() override;
   void OnFinRead() override {}
   void AddBytesConsumed(QuicByteCount /*bytes*/) override {}
-  void Reset(QuicRstStreamErrorCode /*error*/) override {}
+  void ResetWithError(QuicResetStreamError /*error*/) override {}
   void OnUnrecoverableError(QuicErrorCode error,
                             const std::string& details) override;
   void OnUnrecoverableError(QuicErrorCode error,
diff --git a/quic/qbone/qbone_stream_test.cc b/quic/qbone/qbone_stream_test.cc
index fc2ce81..8e724a4 100644
--- a/quic/qbone/qbone_stream_test.cc
+++ b/quic/qbone/qbone_stream_test.cc
@@ -31,11 +31,8 @@
 class MockQuicSession : public QboneSessionBase {
  public:
   MockQuicSession(QuicConnection* connection, const QuicConfig& config)
-      : QboneSessionBase(connection,
-                         nullptr /*visitor*/,
-                         config,
-                         CurrentSupportedVersions(),
-                         nullptr /*writer*/) {}
+      : QboneSessionBase(connection, nullptr /*visitor*/, config,
+                         CurrentSupportedVersions(), nullptr /*writer*/) {}
 
   ~MockQuicSession() override {}
 
@@ -56,16 +53,12 @@
   }
 
   // Called by QuicStream when they want to close stream.
-  MOCK_METHOD(void,
-              MaybeSendRstStreamFrame,
-              (QuicStreamId stream_id,
-               QuicRstStreamErrorCode error,
+  MOCK_METHOD(void, MaybeSendRstStreamFrame,
+              (QuicStreamId stream_id, QuicResetStreamError error,
                QuicStreamOffset bytes_written),
               (override));
-  MOCK_METHOD(void,
-              MaybeSendStopSendingFrame,
-              (QuicStreamId stream_id, QuicRstStreamErrorCode error),
-              (override));
+  MOCK_METHOD(void, MaybeSendStopSendingFrame,
+              (QuicStreamId stream_id, QuicResetStreamError error), (override));
 
   // Sets whether data is written to buffer, or else if this is write blocked.
   void set_writable(bool writable) { writable_ = writable; }
@@ -104,8 +97,7 @@
   DummyPacketWriter() {}
 
   // QuicPacketWriter overrides.
-  WriteResult WritePacket(const char* buffer,
-                          size_t buf_len,
+  WriteResult WritePacket(const char* buffer, size_t buf_len,
                           const QuicIpAddress& self_address,
                           const QuicSocketAddress& peer_address,
                           PerPacketOptions* options) override {
@@ -181,8 +173,7 @@
   SimpleBufferAllocator buffer_allocator_;
   MockClock clock_;
   const QuicStreamId kStreamId = QuicUtils::GetFirstUnidirectionalStreamId(
-      CurrentSupportedVersions()[0].transport_version,
-      Perspective::IS_CLIENT);
+      CurrentSupportedVersions()[0].transport_version, Perspective::IS_CLIENT);
 };
 
 // Read an entire string.
@@ -240,9 +231,13 @@
   std::string packet = "0123456789";
   int iterations = (QboneConstants::kMaxQbonePacketBytes / packet.size()) + 2;
   EXPECT_CALL(*session_, MaybeSendStopSendingFrame(
-                             kStreamId, QUIC_BAD_APPLICATION_PAYLOAD));
-  EXPECT_CALL(*session_, MaybeSendRstStreamFrame(
-                             kStreamId, QUIC_BAD_APPLICATION_PAYLOAD, _));
+                             kStreamId, QuicResetStreamError::FromInternal(
+                                            QUIC_BAD_APPLICATION_PAYLOAD)));
+  EXPECT_CALL(
+      *session_,
+      MaybeSendRstStreamFrame(
+          kStreamId,
+          QuicResetStreamError::FromInternal(QUIC_BAD_APPLICATION_PAYLOAD), _));
   for (int i = 0; i < iterations; ++i) {
     QuicStreamFrame frame(kStreamId, i == (iterations - 1), i * packet.size(),
                           packet);
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h
index fa0362e..0831b81 100644
--- a/quic/test_tools/quic_test_utils.h
+++ b/quic/test_tools/quic_test_utils.h
@@ -24,6 +24,7 @@
 #include "quic/core/http/quic_spdy_session.h"
 #include "quic/core/quic_connection.h"
 #include "quic/core/quic_connection_id.h"
+#include "quic/core/quic_error_codes.h"
 #include "quic/core/quic_framer.h"
 #include "quic/core/quic_packet_writer.h"
 #include "quic/core/quic_path_validator.h"
@@ -777,12 +778,11 @@
   MOCK_METHOD(bool, WriteControlFrame,
               (const QuicFrame& frame, TransmissionType type), (override));
   MOCK_METHOD(void, MaybeSendRstStreamFrame,
-              (QuicStreamId stream_id, QuicRstStreamErrorCode error,
+              (QuicStreamId stream_id, QuicResetStreamError error,
                QuicStreamOffset bytes_written),
               (override));
   MOCK_METHOD(void, MaybeSendStopSendingFrame,
-              (QuicStreamId stream_id, QuicRstStreamErrorCode error),
-              (override));
+              (QuicStreamId stream_id, QuicResetStreamError error), (override));
 
   MOCK_METHOD(bool, ShouldKeepConnectionAlive, (), (const, override));
   MOCK_METHOD(std::vector<std::string>, GetAlpnsToOffer, (), (const, override));
@@ -802,7 +802,8 @@
   void ReallyMaybeSendRstStreamFrame(QuicStreamId id,
                                      QuicRstStreamErrorCode error,
                                      QuicStreamOffset bytes_written) {
-    QuicSession::MaybeSendRstStreamFrame(id, error, bytes_written);
+    QuicSession::MaybeSendRstStreamFrame(
+        id, QuicResetStreamError::FromInternal(error), bytes_written);
   }
 
  private:
@@ -891,12 +892,11 @@
                EncryptionLevel level),
               (override));
   MOCK_METHOD(void, MaybeSendRstStreamFrame,
-              (QuicStreamId stream_id, QuicRstStreamErrorCode error,
+              (QuicStreamId stream_id, QuicResetStreamError error,
                QuicStreamOffset bytes_written),
               (override));
   MOCK_METHOD(void, MaybeSendStopSendingFrame,
-              (QuicStreamId stream_id, QuicRstStreamErrorCode error),
-              (override));
+              (QuicStreamId stream_id, QuicResetStreamError error), (override));
   MOCK_METHOD(void, SendWindowUpdate,
               (QuicStreamId id, QuicStreamOffset byte_offset), (override));
   MOCK_METHOD(void, SendBlocked, (QuicStreamId id), (override));
diff --git a/quic/tools/quic_simple_server_session.cc b/quic/tools/quic_simple_server_session.cc
index 1e5734d..d1e9a73 100644
--- a/quic/tools/quic_simple_server_session.cc
+++ b/quic/tools/quic_simple_server_session.cc
@@ -136,8 +136,9 @@
         promised_streams_[index].is_cancelled = true;
       }
     }
-    control_frame_manager().WriteOrBufferRstStream(frame.stream_id,
-                                                   QUIC_RST_ACKNOWLEDGEMENT, 0);
+    control_frame_manager().WriteOrBufferRstStream(
+        frame.stream_id,
+        QuicResetStreamError::FromInternal(QUIC_RST_ACKNOWLEDGEMENT), 0);
     connection()->OnStreamReset(frame.stream_id, QUIC_RST_ACKNOWLEDGEMENT);
   }
 }
diff --git a/quic/tools/quic_simple_server_stream_test.cc b/quic/tools/quic_simple_server_stream_test.cc
index 50b20b2..aa07703 100644
--- a/quic/tools/quic_simple_server_stream_test.cc
+++ b/quic/tools/quic_simple_server_stream_test.cc
@@ -158,12 +158,11 @@
                const spdy::SpdyStreamPrecedence& precedence),
               (override));
   MOCK_METHOD(void, MaybeSendRstStreamFrame,
-              (QuicStreamId stream_id, QuicRstStreamErrorCode error,
+              (QuicStreamId stream_id, QuicResetStreamError error,
                QuicStreamOffset bytes_written),
               (override));
   MOCK_METHOD(void, MaybeSendStopSendingFrame,
-              (QuicStreamId stream_id, QuicRstStreamErrorCode error),
-              (override));
+              (QuicStreamId stream_id, QuicResetStreamError error), (override));
 
   using QuicSession::ActivateStream;
 
@@ -322,10 +321,15 @@
   stream_->CloseWriteSide();
 
   if (session_.version().UsesHttp3()) {
-    EXPECT_CALL(session_, MaybeSendStopSendingFrame(_, QUIC_STREAM_NO_ERROR))
+    EXPECT_CALL(session_,
+                MaybeSendStopSendingFrame(_, QuicResetStreamError::FromInternal(
+                                                 QUIC_STREAM_NO_ERROR)))
         .Times(1);
   } else {
-    EXPECT_CALL(session_, MaybeSendRstStreamFrame(_, QUIC_STREAM_NO_ERROR, _))
+    EXPECT_CALL(
+        session_,
+        MaybeSendRstStreamFrame(
+            _, QuicResetStreamError::FromInternal(QUIC_STREAM_NO_ERROR), _))
         .Times(1);
   }
   stream_->StopReading();
@@ -452,11 +456,16 @@
 
   InSequence s;
   if (session_.version().UsesHttp3()) {
-    EXPECT_CALL(session_, MaybeSendStopSendingFrame(promised_stream->id(),
-                                                    QUIC_STREAM_CANCELLED));
+    EXPECT_CALL(session_,
+                MaybeSendStopSendingFrame(
+                    promised_stream->id(),
+                    QuicResetStreamError::FromInternal(QUIC_STREAM_CANCELLED)));
   }
-  EXPECT_CALL(session_, MaybeSendRstStreamFrame(promised_stream->id(),
-                                                QUIC_STREAM_CANCELLED, 0));
+  EXPECT_CALL(
+      session_,
+      MaybeSendRstStreamFrame(
+          promised_stream->id(),
+          QuicResetStreamError::FromInternal(QUIC_STREAM_CANCELLED), 0));
 
   promised_stream->DoSendResponse();
 }
@@ -667,11 +676,14 @@
         .Times(AnyNumber());
   }
 
-  EXPECT_CALL(session_, MaybeSendRstStreamFrame(_,
-                                                session_.version().UsesHttp3()
-                                                    ? QUIC_STREAM_CANCELLED
-                                                    : QUIC_RST_ACKNOWLEDGEMENT,
-                                                _))
+  EXPECT_CALL(
+      session_,
+      MaybeSendRstStreamFrame(
+          _,
+          session_.version().UsesHttp3()
+              ? QuicResetStreamError::FromInternal(QUIC_STREAM_CANCELLED)
+              : QuicResetStreamError::FromInternal(QUIC_RST_ACKNOWLEDGEMENT),
+          _))
       .Times(1);
   QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream_->id(),
                                QUIC_STREAM_CANCELLED, 1234);