Add HTTP/3 error codes, use them in HttpDecoder and for unexpected frames in QuicSpdyStream and QuicReceiveControlStream.

ENUM_VALUE_OK=Only changing value of sentinel entry QUIC_LAST_ERROR.

gfe-relnote: n/a, change to QUIC v99-only code.  Protected by existing disabled gfe2_reloadable_flag_quic_enable_version_99.
PiperOrigin-RevId: 290374408
Change-Id: I90837f021f0dd585f8015d2fcae37d0d0eacfed6
diff --git a/quic/core/http/http_decoder.cc b/quic/core/http/http_decoder.cc
index f8a73f4..c5aa08c 100644
--- a/quic/core/http/http_decoder.cc
+++ b/quic/core/http/http_decoder.cc
@@ -128,8 +128,7 @@
   }
 
   if (current_frame_length_ > MaxFrameLength(current_frame_type_)) {
-    // TODO(b/124216424): Use HTTP_EXCESSIVE_LOAD.
-    RaiseError(QUIC_INVALID_FRAME_DATA, "Frame is too large");
+    RaiseError(QUIC_HTTP_FRAME_TOO_LARGE, "Frame is too large.");
     return false;
   }
 
@@ -155,7 +154,8 @@
       // This edge case needs to be handled here, because ReadFramePayload()
       // does not get called if |current_frame_length_| is zero.
       if (current_frame_length_ == 0) {
-        RaiseError(QUIC_INVALID_FRAME_DATA, "Corrupt PUSH_PROMISE frame.");
+        RaiseError(QUIC_HTTP_FRAME_ERROR,
+                   "PUSH_PROMISE frame with empty payload.");
         return false;
       }
       continue_processing = visitor_->OnPushPromiseFrameStart(header_length);
@@ -225,7 +225,8 @@
         DCHECK_EQ(0u, current_push_id_length_);
         current_push_id_length_ = reader->PeekVarInt62Length();
         if (current_push_id_length_ > remaining_frame_length_) {
-          RaiseError(QUIC_INVALID_FRAME_DATA, "PUSH_PROMISE frame malformed.");
+          RaiseError(QUIC_HTTP_FRAME_ERROR,
+                     "Unable to read PUSH_PROMISE push_id.");
           return false;
         }
         if (current_push_id_length_ > reader->BytesRemaining()) {
@@ -336,12 +337,12 @@
       CancelPushFrame frame;
       QuicDataReader reader(buffer_.data(), current_frame_length_);
       if (!reader.ReadVarInt62(&frame.push_id)) {
-        // TODO(b/124216424): Use HTTP_MALFORMED_FRAME.
-        RaiseError(QUIC_INVALID_FRAME_DATA, "Unable to read push_id");
+        RaiseError(QUIC_HTTP_FRAME_ERROR,
+                   "Unable to read CANCEL_PUSH push_id.");
         return false;
       }
       if (!reader.IsDoneReading()) {
-        RaiseError(QUIC_INVALID_FRAME_DATA,
+        RaiseError(QUIC_HTTP_FRAME_ERROR,
                    "Superfluous data in CANCEL_PUSH frame.");
         return false;
       }
@@ -370,13 +371,11 @@
                     "QuicStreamId from uint32_t to uint64_t.");
       uint64_t stream_id;
       if (!reader.ReadVarInt62(&stream_id)) {
-        // TODO(b/124216424): Use HTTP_MALFORMED_FRAME.
-        RaiseError(QUIC_INVALID_FRAME_DATA, "Unable to read GOAWAY stream_id");
+        RaiseError(QUIC_HTTP_FRAME_ERROR, "Unable to read GOAWAY stream_id.");
         return false;
       }
       if (!reader.IsDoneReading()) {
-        RaiseError(QUIC_INVALID_FRAME_DATA,
-                   "Superfluous data in GOAWAY frame.");
+        RaiseError(QUIC_HTTP_FRAME_ERROR, "Superfluous data in GOAWAY frame.");
         return false;
       }
       frame.stream_id = static_cast<QuicStreamId>(stream_id);
@@ -387,12 +386,12 @@
       QuicDataReader reader(buffer_.data(), current_frame_length_);
       MaxPushIdFrame frame;
       if (!reader.ReadVarInt62(&frame.push_id)) {
-        // TODO(b/124216424): Use HTTP_MALFORMED_FRAME.
-        RaiseError(QUIC_INVALID_FRAME_DATA, "Unable to read push_id");
+        RaiseError(QUIC_HTTP_FRAME_ERROR,
+                   "Unable to read MAX_PUSH_ID push_id.");
         return false;
       }
       if (!reader.IsDoneReading()) {
-        RaiseError(QUIC_INVALID_FRAME_DATA,
+        RaiseError(QUIC_HTTP_FRAME_ERROR,
                    "Superfluous data in MAX_PUSH_ID frame.");
         return false;
       }
@@ -403,12 +402,12 @@
       QuicDataReader reader(buffer_.data(), current_frame_length_);
       DuplicatePushFrame frame;
       if (!reader.ReadVarInt62(&frame.push_id)) {
-        // TODO(b/124216424): Use HTTP_MALFORMED_FRAME.
-        RaiseError(QUIC_INVALID_FRAME_DATA, "Unable to read push_id");
+        RaiseError(QUIC_HTTP_FRAME_ERROR,
+                   "Unable to read DUPLICATE_PUSH push_id.");
         return false;
       }
       if (!reader.IsDoneReading()) {
-        RaiseError(QUIC_INVALID_FRAME_DATA,
+        RaiseError(QUIC_HTTP_FRAME_ERROR,
                    "Superfluous data in DUPLICATE_PUSH frame.");
         return false;
       }
@@ -513,22 +512,17 @@
   while (!reader->IsDoneReading()) {
     uint64_t id;
     if (!reader->ReadVarInt62(&id)) {
-      // TODO(b/124216424): Use HTTP_MALFORMED_FRAME.
-      RaiseError(QUIC_INVALID_FRAME_DATA,
-                 "Unable to read settings frame identifier");
+      RaiseError(QUIC_HTTP_FRAME_ERROR, "Unable to read setting identifier.");
       return false;
     }
     uint64_t content;
     if (!reader->ReadVarInt62(&content)) {
-      // TODO(b/124216424): Use HTTP_MALFORMED_FRAME.
-      RaiseError(QUIC_INVALID_FRAME_DATA,
-                 "Unable to read settings frame content");
+      RaiseError(QUIC_HTTP_FRAME_ERROR, "Unable to read setting value.");
       return false;
     }
     auto result = frame->values.insert({id, content});
     if (!result.second) {
-      // TODO(b/124216424): Use HTTP_SETTINGS_ERROR.
-      RaiseError(QUIC_INVALID_FRAME_DATA, "Duplicate SETTINGS identifier.");
+      RaiseError(QUIC_HTTP_FRAME_ERROR, "Duplicate setting identifier.");
       return false;
     }
   }
@@ -539,16 +533,14 @@
                                            PriorityUpdateFrame* frame) {
   uint8_t prioritized_element_type;
   if (!reader->ReadUInt8(&prioritized_element_type)) {
-    // TODO(b/124216424): Use HTTP_MALFORMED_FRAME.
-    RaiseError(QUIC_INVALID_FRAME_DATA,
+    RaiseError(QUIC_HTTP_FRAME_ERROR,
                "Unable to read prioritized element type.");
     return false;
   }
 
   if (prioritized_element_type != REQUEST_STREAM &&
       prioritized_element_type != PUSH_STREAM) {
-    // TODO(b/124216424): Use HTTP_MALFORMED_FRAME.
-    RaiseError(QUIC_INVALID_FRAME_DATA, "Invalid prioritized element type.");
+    RaiseError(QUIC_HTTP_FRAME_ERROR, "Invalid prioritized element type.");
     return false;
   }
 
@@ -556,9 +548,7 @@
       static_cast<PrioritizedElementType>(prioritized_element_type);
 
   if (!reader->ReadVarInt62(&frame->prioritized_element_id)) {
-    // TODO(b/124216424): Use HTTP_MALFORMED_FRAME.
-    RaiseError(QUIC_INVALID_FRAME_DATA,
-               "Unable to read prioritized element id.");
+    RaiseError(QUIC_HTTP_FRAME_ERROR, "Unable to read prioritized element id.");
     return false;
   }
 
diff --git a/quic/core/http/http_decoder_test.cc b/quic/core/http/http_decoder_test.cc
index 3a67395..7cd0853 100644
--- a/quic/core/http/http_decoder_test.cc
+++ b/quic/core/http/http_decoder_test.cc
@@ -303,8 +303,8 @@
 
     decoder.ProcessInput(input.data(), input.size());
 
-    EXPECT_THAT(decoder.error(), IsError(QUIC_INVALID_FRAME_DATA));
-    EXPECT_EQ("PUSH_PROMISE frame malformed.", decoder.error_detail());
+    EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR));
+    EXPECT_EQ("Unable to read PUSH_PROMISE push_id.", decoder.error_detail());
   }
   {
     HttpDecoder decoder(&visitor_);
@@ -315,8 +315,8 @@
       decoder.ProcessInput(&c, 1);
     }
 
-    EXPECT_THAT(decoder.error(), IsError(QUIC_INVALID_FRAME_DATA));
-    EXPECT_EQ("PUSH_PROMISE frame malformed.", decoder.error_detail());
+    EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR));
+    EXPECT_EQ("Unable to read PUSH_PROMISE push_id.", decoder.error_detail());
   }
 }
 
@@ -430,10 +430,10 @@
     size_t payload_length;
     const char* const error_message;
   } kTestData[] = {
-      {1, "Unable to read settings frame identifier"},
-      {5, "Unable to read settings frame content"},
-      {7, "Unable to read settings frame identifier"},
-      {12, "Unable to read settings frame content"},
+      {1, "Unable to read setting identifier."},
+      {5, "Unable to read setting value."},
+      {7, "Unable to read setting identifier."},
+      {12, "Unable to read setting value."},
   };
 
   for (const auto& test_data : kTestData) {
@@ -450,7 +450,7 @@
     QuicByteCount processed_bytes =
         decoder.ProcessInput(input.data(), input.size());
     EXPECT_EQ(input.size(), processed_bytes);
-    EXPECT_THAT(decoder.error(), IsError(QUIC_INVALID_FRAME_DATA));
+    EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR));
     EXPECT_EQ(test_data.error_message, decoder.error_detail());
   }
 }
@@ -469,8 +469,8 @@
 
   EXPECT_EQ(input.size(), ProcessInput(input));
 
-  EXPECT_THAT(decoder_.error(), IsError(QUIC_INVALID_FRAME_DATA));
-  EXPECT_EQ("Duplicate SETTINGS identifier.", decoder_.error_detail());
+  EXPECT_THAT(decoder_.error(), IsError(QUIC_HTTP_FRAME_ERROR));
+  EXPECT_EQ("Duplicate setting identifier.", decoder_.error_detail());
 }
 
 TEST_F(HttpDecoderTest, DataFrame) {
@@ -753,8 +753,8 @@
   // Process the full frame.
   EXPECT_CALL(visitor_, OnError(&decoder_));
   EXPECT_EQ(2u, ProcessInput(input));
-  EXPECT_THAT(decoder_.error(), IsError(QUIC_INVALID_FRAME_DATA));
-  EXPECT_EQ("Frame is too large", decoder_.error_detail());
+  EXPECT_THAT(decoder_.error(), IsError(QUIC_HTTP_FRAME_TOO_LARGE));
+  EXPECT_EQ("Frame is too large.", decoder_.error_detail());
 }
 
 TEST_F(HttpDecoderTest, MalformedSettingsFrame) {
@@ -768,8 +768,8 @@
   writer.WriteStringPiece("Malformed payload");
   EXPECT_CALL(visitor_, OnError(&decoder_));
   EXPECT_EQ(5u, decoder_.ProcessInput(input, QUICHE_ARRAYSIZE(input)));
-  EXPECT_THAT(decoder_.error(), IsError(QUIC_INVALID_FRAME_DATA));
-  EXPECT_EQ("Frame is too large", decoder_.error_detail());
+  EXPECT_THAT(decoder_.error(), IsError(QUIC_HTTP_FRAME_TOO_LARGE));
+  EXPECT_EQ("Frame is too large.", decoder_.error_detail());
 }
 
 TEST_F(HttpDecoderTest, HeadersPausedThenData) {
@@ -814,7 +814,7 @@
   } kTestData[] = {{"\x03"   // type (CANCEL_PUSH)
                     "\x01"   // length
                     "\x40",  // first byte of two-byte varint push id
-                    "Unable to read push_id"},
+                    "Unable to read CANCEL_PUSH push_id."},
                    {"\x03"  // type (CANCEL_PUSH)
                     "\x04"  // length
                     "\x05"  // valid push id
@@ -823,7 +823,7 @@
                    {"\x0D"   // type (MAX_PUSH_ID)
                     "\x01"   // length
                     "\x40",  // first byte of two-byte varint push id
-                    "Unable to read push_id"},
+                    "Unable to read MAX_PUSH_ID push_id."},
                    {"\x0D"  // type (MAX_PUSH_ID)
                     "\x04"  // length
                     "\x05"  // valid push id
@@ -832,7 +832,7 @@
                    {"\x0E"   // type (DUPLICATE_PUSH)
                     "\x01"   // length
                     "\x40",  // first byte of two-byte varint push id
-                    "Unable to read push_id"},
+                    "Unable to read DUPLICATE_PUSH push_id."},
                    {"\x0E"  // type (DUPLICATE_PUSH)
                     "\x04"  // length
                     "\x05"  // valid push id
@@ -841,7 +841,7 @@
                    {"\x07"   // type (GOAWAY)
                     "\x01"   // length
                     "\x40",  // first byte of two-byte varint stream id
-                    "Unable to read GOAWAY stream_id"},
+                    "Unable to read GOAWAY stream_id."},
                    {"\x07"  // type (GOAWAY)
                     "\x04"  // length
                     "\x05"  // valid stream id
@@ -855,7 +855,7 @@
 
       quiche::QuicheStringPiece input(test_data.input);
       decoder.ProcessInput(input.data(), input.size());
-      EXPECT_THAT(decoder.error(), IsError(QUIC_INVALID_FRAME_DATA));
+      EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR));
       EXPECT_EQ(test_data.error_message, decoder.error_detail());
     }
     {
@@ -866,7 +866,7 @@
       for (auto c : input) {
         decoder.ProcessInput(&c, 1);
       }
-      EXPECT_THAT(decoder.error(), IsError(QUIC_INVALID_FRAME_DATA));
+      EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR));
       EXPECT_EQ(test_data.error_message, decoder.error_detail());
     }
   }
@@ -879,8 +879,8 @@
 
   EXPECT_CALL(visitor_, OnError(&decoder_));
   EXPECT_EQ(input.size(), ProcessInput(input));
-  EXPECT_THAT(decoder_.error(), IsError(QUIC_INVALID_FRAME_DATA));
-  EXPECT_EQ("Unable to read push_id", decoder_.error_detail());
+  EXPECT_THAT(decoder_.error(), IsError(QUIC_HTTP_FRAME_ERROR));
+  EXPECT_EQ("Unable to read CANCEL_PUSH push_id.", decoder_.error_detail());
 }
 
 TEST_F(HttpDecoderTest, EmptySettingsFrame) {
@@ -906,8 +906,8 @@
 
   EXPECT_CALL(visitor_, OnError(&decoder_));
   EXPECT_EQ(input.size(), ProcessInput(input));
-  EXPECT_THAT(decoder_.error(), IsError(QUIC_INVALID_FRAME_DATA));
-  EXPECT_EQ("Corrupt PUSH_PROMISE frame.", decoder_.error_detail());
+  EXPECT_THAT(decoder_.error(), IsError(QUIC_HTTP_FRAME_ERROR));
+  EXPECT_EQ("PUSH_PROMISE frame with empty payload.", decoder_.error_detail());
 }
 
 TEST_F(HttpDecoderTest, EmptyGoAwayFrame) {
@@ -917,8 +917,8 @@
 
   EXPECT_CALL(visitor_, OnError(&decoder_));
   EXPECT_EQ(input.size(), ProcessInput(input));
-  EXPECT_THAT(decoder_.error(), IsError(QUIC_INVALID_FRAME_DATA));
-  EXPECT_EQ("Unable to read GOAWAY stream_id", decoder_.error_detail());
+  EXPECT_THAT(decoder_.error(), IsError(QUIC_HTTP_FRAME_ERROR));
+  EXPECT_EQ("Unable to read GOAWAY stream_id.", decoder_.error_detail());
 }
 
 TEST_F(HttpDecoderTest, EmptyMaxPushIdFrame) {
@@ -928,8 +928,8 @@
 
   EXPECT_CALL(visitor_, OnError(&decoder_));
   EXPECT_EQ(input.size(), ProcessInput(input));
-  EXPECT_THAT(decoder_.error(), IsError(QUIC_INVALID_FRAME_DATA));
-  EXPECT_EQ("Unable to read push_id", decoder_.error_detail());
+  EXPECT_THAT(decoder_.error(), IsError(QUIC_HTTP_FRAME_ERROR));
+  EXPECT_EQ("Unable to read MAX_PUSH_ID push_id.", decoder_.error_detail());
 }
 
 TEST_F(HttpDecoderTest, EmptyDuplicatePushFrame) {
@@ -939,8 +939,8 @@
 
   EXPECT_CALL(visitor_, OnError(&decoder_));
   EXPECT_EQ(input.size(), ProcessInput(input));
-  EXPECT_THAT(decoder_.error(), IsError(QUIC_INVALID_FRAME_DATA));
-  EXPECT_EQ("Unable to read push_id", decoder_.error_detail());
+  EXPECT_THAT(decoder_.error(), IsError(QUIC_HTTP_FRAME_ERROR));
+  EXPECT_EQ("Unable to read DUPLICATE_PUSH push_id.", decoder_.error_detail());
 }
 
 TEST_F(HttpDecoderTest, LargeStreamIdInGoAway) {
@@ -1068,7 +1068,7 @@
     QuicByteCount processed_bytes =
         decoder.ProcessInput(input.data(), input.size());
     EXPECT_EQ(input.size(), processed_bytes);
-    EXPECT_THAT(decoder.error(), IsError(QUIC_INVALID_FRAME_DATA));
+    EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR));
     EXPECT_EQ(test_data.error_message, decoder.error_detail());
   }
 }
diff --git a/quic/core/http/quic_receive_control_stream.cc b/quic/core/http/quic_receive_control_stream.cc
index 283b54e..f07e900 100644
--- a/quic/core/http/quic_receive_control_stream.cc
+++ b/quic/core/http/quic_receive_control_stream.cc
@@ -24,9 +24,9 @@
   HttpDecoderVisitor(const HttpDecoderVisitor&) = delete;
   HttpDecoderVisitor& operator=(const HttpDecoderVisitor&) = delete;
 
-  void OnError(HttpDecoder* /*decoder*/) override {
+  void OnError(HttpDecoder* decoder) override {
     stream_->session()->connection()->CloseConnection(
-        QUIC_HTTP_DECODER_ERROR, "Http decoder internal error",
+        decoder->error(), decoder->error_detail(),
         ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
   }
 
@@ -150,9 +150,8 @@
 
  private:
   void CloseConnectionOnWrongFrame(quiche::QuicheStringPiece frame_type) {
-    // TODO(renjietang): Change to HTTP/3 error type.
     stream_->session()->connection()->CloseConnection(
-        QUIC_HTTP_DECODER_ERROR,
+        QUIC_HTTP_FRAME_UNEXPECTED_ON_CONTROL_STREAM,
         quiche::QuicheStrCat(frame_type, " frame received on control stream"),
         ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
   }
diff --git a/quic/core/http/quic_receive_control_stream_test.cc b/quic/core/http/quic_receive_control_stream_test.cc
index b603efc..c669f85 100644
--- a/quic/core/http/quic_receive_control_stream_test.cc
+++ b/quic/core/http/quic_receive_control_stream_test.cc
@@ -214,7 +214,9 @@
   std::string data = std::string(buffer.get(), header_length);
 
   QuicStreamFrame frame(receive_control_stream_->id(), false, 1, data);
-  EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_DECODER_ERROR, _, _));
+  EXPECT_CALL(
+      *connection_,
+      CloseConnection(QUIC_HTTP_FRAME_UNEXPECTED_ON_CONTROL_STREAM, _, _));
   receive_control_stream_->OnStreamFrame(frame);
 }
 
@@ -231,7 +233,9 @@
   EXPECT_FALSE(session_.http3_goaway_received());
 
   if (perspective() == Perspective::IS_SERVER) {
-    EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_DECODER_ERROR, _, _));
+    EXPECT_CALL(
+        *connection_,
+        CloseConnection(QUIC_HTTP_FRAME_UNEXPECTED_ON_CONTROL_STREAM, _, _));
   }
 
   receive_control_stream_->OnStreamFrame(frame);
@@ -249,8 +253,9 @@
       push_promise, &buffer);
   QuicStreamFrame frame(receive_control_stream_->id(), false, 1, buffer.get(),
                         length);
-  // TODO(lassey) Check for HTTP_WRONG_STREAM error code.
-  EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_DECODER_ERROR, _, _))
+  EXPECT_CALL(
+      *connection_,
+      CloseConnection(QUIC_HTTP_FRAME_UNEXPECTED_ON_CONTROL_STREAM, _, _))
       .WillOnce(
           Invoke(connection_, &MockQuicConnection::ReallyCloseConnection));
   EXPECT_CALL(*connection_, SendConnectionClosePacket(_, _));
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
index 195f92b..5d5c176 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -40,9 +40,9 @@
   HttpDecoderVisitor(const HttpDecoderVisitor&) = delete;
   HttpDecoderVisitor& operator=(const HttpDecoderVisitor&) = delete;
 
-  void OnError(HttpDecoder* /*decoder*/) override {
+  void OnError(HttpDecoder* decoder) override {
     stream_->session()->connection()->CloseConnection(
-        QUIC_HTTP_DECODER_ERROR, "Http decoder internal error",
+        decoder->error(), decoder->error_detail(),
         ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
   }
 
@@ -171,7 +171,7 @@
  private:
   void CloseConnectionOnWrongFrame(quiche::QuicheStringPiece frame_type) {
     stream_->session()->connection()->CloseConnection(
-        QUIC_HTTP_DECODER_ERROR,
+        QUIC_HTTP_FRAME_UNEXPECTED_ON_SPDY_STREAM,
         quiche::QuicheStrCat(frame_type, " frame received on data stream"),
         ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
   }
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc
index 164b119..8616ab7 100644
--- a/quic/core/http/quic_spdy_stream_test.cc
+++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -497,7 +497,8 @@
   QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), false, 0,
                         quiche::QuicheStringPiece(data));
 
-  EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_DECODER_ERROR, _, _))
+  EXPECT_CALL(*connection_,
+              CloseConnection(QUIC_HTTP_FRAME_UNEXPECTED_ON_SPDY_STREAM, _, _))
       .WillOnce(
           (Invoke([this](QuicErrorCode error, const std::string& error_details,
                          ConnectionCloseBehavior connection_close_behavior) {
@@ -516,6 +517,39 @@
   stream_->OnStreamFrame(frame);
 }
 
+TEST_P(QuicSpdyStreamTest, Http3FrameError) {
+  if (!UsesHttp3()) {
+    return;
+  }
+
+  Initialize(kShouldProcessData);
+
+  // PUSH_PROMISE frame with empty payload is considered invalid.
+  std::string invalid_http3_frame = quiche::QuicheTextUtils::HexDecode("0500");
+  QuicStreamFrame stream_frame(stream_->id(), /* fin = */ false,
+                               /* offset = */ 0, invalid_http3_frame);
+
+  EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_FRAME_ERROR, _, _));
+  stream_->OnStreamFrame(stream_frame);
+}
+
+TEST_P(QuicSpdyStreamTest, UnexpectedHttp3Frame) {
+  if (!UsesHttp3()) {
+    return;
+  }
+
+  Initialize(kShouldProcessData);
+
+  // SETTINGS frame with empty payload.
+  std::string settings = quiche::QuicheTextUtils::HexDecode("0400");
+  QuicStreamFrame stream_frame(stream_->id(), /* fin = */ false,
+                               /* offset = */ 0, settings);
+
+  EXPECT_CALL(*connection_,
+              CloseConnection(QUIC_HTTP_FRAME_UNEXPECTED_ON_SPDY_STREAM, _, _));
+  stream_->OnStreamFrame(stream_frame);
+}
+
 TEST_P(QuicSpdyStreamTest, ProcessHeadersAndBody) {
   Initialize(kShouldProcessData);
 
@@ -2553,7 +2587,8 @@
 
   EXPECT_EQ(0u, stream_->sequencer()->NumBytesConsumed());
 
-  EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_DECODER_ERROR, _, _))
+  EXPECT_CALL(*connection_,
+              CloseConnection(QUIC_HTTP_FRAME_UNEXPECTED_ON_SPDY_STREAM, _, _))
       .WillOnce(
           Invoke(connection_, &MockQuicConnection::ReallyCloseConnection));
   EXPECT_CALL(*connection_, SendConnectionClosePacket(_, _));
diff --git a/quic/core/quic_error_codes.cc b/quic/core/quic_error_codes.cc
index ae17f09..7aa0dff 100644
--- a/quic/core/quic_error_codes.cc
+++ b/quic/core/quic_error_codes.cc
@@ -165,6 +165,10 @@
     RETURN_STRING_LITERAL(QUIC_QPACK_DECODER_STREAM_ERROR);
     RETURN_STRING_LITERAL(QUIC_STREAM_DATA_BEYOND_CLOSE_OFFSET);
     RETURN_STRING_LITERAL(QUIC_STREAM_MULTIPLE_OFFSET);
+    RETURN_STRING_LITERAL(QUIC_HTTP_FRAME_TOO_LARGE);
+    RETURN_STRING_LITERAL(QUIC_HTTP_FRAME_ERROR);
+    RETURN_STRING_LITERAL(QUIC_HTTP_FRAME_UNEXPECTED_ON_SPDY_STREAM);
+    RETURN_STRING_LITERAL(QUIC_HTTP_FRAME_UNEXPECTED_ON_CONTROL_STREAM);
 
     RETURN_STRING_LITERAL(QUIC_LAST_ERROR);
     // Intentionally have no default case, so we'll break the build
diff --git a/quic/core/quic_error_codes.h b/quic/core/quic_error_codes.h
index b6c2d50..9a08844 100644
--- a/quic/core/quic_error_codes.h
+++ b/quic/core/quic_error_codes.h
@@ -353,8 +353,14 @@
   // Received multiple close offset.
   QUIC_STREAM_MULTIPLE_OFFSET = 130,
 
+  // Internal error codes for HTTP/3 errors.
+  QUIC_HTTP_FRAME_TOO_LARGE = 131,
+  QUIC_HTTP_FRAME_ERROR = 132,
+  QUIC_HTTP_FRAME_UNEXPECTED_ON_SPDY_STREAM = 133,
+  QUIC_HTTP_FRAME_UNEXPECTED_ON_CONTROL_STREAM = 134,
+
   // No error. Used as bound while iterating.
-  QUIC_LAST_ERROR = 131,
+  QUIC_LAST_ERROR = 135,
 };
 // QuicErrorCodes is encoded as four octets on-the-wire when doing Google QUIC,
 // or a varint62 when doing IETF QUIC. Ensure that its value does not exceed
@@ -370,6 +376,27 @@
 // Returns the name of the QuicErrorCode as a char*
 QUIC_EXPORT_PRIVATE const char* QuicErrorCodeToString(QuicErrorCode error);
 
+// Wire values for HTTP/3 errors.
+// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#http-error-codes
+enum class QuicHttp3ErrorCode {
+  IETF_QUIC_HTTP3_NO_ERROR = 0x100,
+  IETF_QUIC_HTTP3_GENERAL_PROTOCOL_ERROR = 0x101,
+  IETF_QUIC_HTTP3_INTERNAL_ERROR = 0x102,
+  IETF_QUIC_HTTP3_STREAM_CREATION_ERROR = 0x103,
+  IETF_QUIC_HTTP3_CLOSED_CRITICAL_STREAM = 0x104,
+  IETF_QUIC_HTTP3_FRAME_UNEXPECTED = 0x105,
+  IETF_QUIC_HTTP3_FRAME_ERROR = 0x106,
+  IETF_QUIC_HTTP3_EXCESSIVE_LOAD = 0x107,
+  IETF_QUIC_HTTP3_ID_ERROR = 0x108,
+  IETF_QUIC_HTTP3_SETTINGS_ERROR = 0x109,
+  IETF_QUIC_HTTP3_MISSING_SETTINGS = 0x10A,
+  IETF_QUIC_HTTP3_REQUEST_REJECTED = 0x10B,
+  IETF_QUIC_HTTP3_REQUEST_CANCELLED = 0x10C,
+  IETF_QUIC_HTTP3_REQUEST_INCOMPLETE = 0x10D,
+  IETF_QUIC_HTTP3_CONNECT_ERROR = 0x10F,
+  IETF_QUIC_HTTP3_VERSION_FALLBACK = 0x110,
+};
+
 // Wire values for QPACK errors.
 // https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#error-code-registration
 enum QuicHttpQpackErrorCode {
diff --git a/quic/core/quic_types.cc b/quic/core/quic_types.cc
index 709f89a..351cd10 100644
--- a/quic/core/quic_types.cc
+++ b/quic/core/quic_types.cc
@@ -425,6 +425,22 @@
               {static_cast<uint64_t>(QUIC_STREAM_DATA_BEYOND_CLOSE_OFFSET)}};
     case QUIC_STREAM_MULTIPLE_OFFSET:
       return {true, {static_cast<uint64_t>(QUIC_STREAM_MULTIPLE_OFFSET)}};
+    case QUIC_HTTP_FRAME_TOO_LARGE:
+      return {false,
+              {static_cast<uint64_t>(
+                  QuicHttp3ErrorCode::IETF_QUIC_HTTP3_EXCESSIVE_LOAD)}};
+    case QUIC_HTTP_FRAME_ERROR:
+      return {false,
+              {static_cast<uint64_t>(
+                  QuicHttp3ErrorCode::IETF_QUIC_HTTP3_FRAME_ERROR)}};
+    case QUIC_HTTP_FRAME_UNEXPECTED_ON_SPDY_STREAM:
+      return {false,
+              {static_cast<uint64_t>(
+                  QuicHttp3ErrorCode::IETF_QUIC_HTTP3_FRAME_UNEXPECTED)}};
+    case QUIC_HTTP_FRAME_UNEXPECTED_ON_CONTROL_STREAM:
+      return {false,
+              {static_cast<uint64_t>(
+                  QuicHttp3ErrorCode::IETF_QUIC_HTTP3_FRAME_UNEXPECTED)}};
     case QUIC_LAST_ERROR:
       return {false, {static_cast<uint64_t>(QUIC_LAST_ERROR)}};
   }