Use structured headers parser for PRIORITY_UPDATE frames.

Use structured headers parser to parse HTTP/3 PRIORITY_UPDATE frame Priority
Field Value field.  Also, do not reset the stream when receiving urgency
parameter with out-of-range value or unexpected type, in accordance with
RFC9218.

Protected by FLAGS_quic_reloadable_flag_quic_priority_update_structured_headers_parser.

PiperOrigin-RevId: 491332082
diff --git a/quiche/quic/core/http/quic_receive_control_stream.cc b/quiche/quic/core/http/quic_receive_control_stream.cc
index 3c1f3e7..a35a925 100644
--- a/quiche/quic/core/http/quic_receive_control_stream.cc
+++ b/quiche/quic/core/http/quic_receive_control_stream.cc
@@ -16,6 +16,7 @@
 #include "quiche/quic/platform/api/quic_flag_utils.h"
 #include "quiche/quic/platform/api/quic_flags.h"
 #include "quiche/common/quiche_text_utils.h"
+#include "quiche/common/structured_headers.h"
 
 namespace quic {
 
@@ -135,7 +136,23 @@
     spdy_session()->debug_visitor()->OnPriorityUpdateFrameReceived(frame);
   }
 
-  // TODO(b/147306124): Use a proper structured headers parser instead.
+  if (GetQuicReloadableFlag(quic_priority_update_structured_headers_parser)) {
+    QUIC_RELOADABLE_FLAG_COUNT(quic_priority_update_structured_headers_parser);
+    const ParsePriorityFieldValueResult result =
+        ParsePriorityFieldValue(frame.priority_field_value);
+
+    if (!result.success) {
+      stream_delegate()->OnStreamError(
+          QUIC_INVALID_PRIORITY_UPDATE,
+          "Invalid PRIORITY_UPDATE frame payload.");
+      return false;
+    }
+
+    const QuicStreamId stream_id = frame.prioritized_element_id;
+    return spdy_session_->OnPriorityUpdateForRequestStream(stream_id,
+                                                           result.urgency);
+  }
+
   for (absl::string_view key_value :
        absl::StrSplit(frame.priority_field_value, ',')) {
     std::vector<absl::string_view> key_and_value =
@@ -152,6 +169,8 @@
 
     absl::string_view value = key_and_value[1];
     int urgency;
+    // This violates RFC9218 Section 4: "priority parameters with out-of-range
+    // values, or values of unexpected types MUST be ignored".
     if (!absl::SimpleAtoi(value, &urgency) || urgency < 0 || urgency > 7) {
       stream_delegate()->OnStreamError(
           QUIC_INVALID_PRIORITY_UPDATE,
@@ -206,6 +225,45 @@
   return true;
 }
 
+QuicReceiveControlStream::ParsePriorityFieldValueResult
+QuicReceiveControlStream::ParsePriorityFieldValue(
+    absl::string_view priority_field_value) {
+  // Default values
+  int urgency = 3;
+  bool incremental = false;
+
+  absl::optional<quiche::structured_headers::Dictionary> parsed_dictionary =
+      quiche::structured_headers::ParseDictionary(priority_field_value);
+  if (!parsed_dictionary.has_value()) {
+    return {false, urgency, incremental};
+  }
+
+  for (const auto& [name, value] : *parsed_dictionary) {
+    if (value.member_is_inner_list) {
+      continue;
+    }
+
+    const std::vector<quiche::structured_headers::ParameterizedItem>& member =
+        value.member;
+    if (member.size() != 1) {
+      continue;
+    }
+
+    const quiche::structured_headers::Item item = member[0].item;
+    if (name == "u" && item.is_integer()) {
+      int parsed_urgency = item.GetInteger();
+      // Ignore out-of-range values.
+      if (parsed_urgency >= 0 && parsed_urgency <= 7) {
+        urgency = parsed_urgency;
+      }
+    } else if (name == "i" && item.is_boolean()) {
+      incremental = item.GetBoolean();
+    }
+  }
+
+  return {true, urgency, incremental};
+}
+
 bool QuicReceiveControlStream::OnUnknownFrameEnd() {
   // Ignore unknown frame types.
   return true;
diff --git a/quiche/quic/core/http/quic_receive_control_stream.h b/quiche/quic/core/http/quic_receive_control_stream.h
index 71d0bf2..e5eeb01 100644
--- a/quiche/quic/core/http/quic_receive_control_stream.h
+++ b/quiche/quic/core/http/quic_receive_control_stream.h
@@ -20,6 +20,13 @@
     : public QuicStream,
       public HttpDecoder::Visitor {
  public:
+  // Return type of ParsePriorityFieldValue().
+  struct ParsePriorityFieldValueResult {
+    bool success;
+    int urgency;
+    bool incremental;
+  };
+
   explicit QuicReceiveControlStream(PendingStream* pending,
                                     QuicSpdySession* spdy_session);
   QuicReceiveControlStream(const QuicReceiveControlStream&) = delete;
@@ -60,6 +67,10 @@
 
   QuicSpdySession* spdy_session() { return spdy_session_; }
 
+  // Parses the Priority Field Value field of a PRIORITY_UPDATE frame.
+  static ParsePriorityFieldValueResult ParsePriorityFieldValue(
+      absl::string_view priority_field_value);
+
  private:
   // Called when a frame of allowed type is received.  Returns true if the frame
   // is allowed in this position.  Returns false and resets the stream
diff --git a/quiche/quic/core/http/quic_receive_control_stream_test.cc b/quiche/quic/core/http/quic_receive_control_stream_test.cc
index af7e5d4..9181ba6 100644
--- a/quiche/quic/core/http/quic_receive_control_stream_test.cc
+++ b/quiche/quic/core/http/quic_receive_control_stream_test.cc
@@ -456,6 +456,71 @@
                       /* offset = */ 1, unknown_frame));
 }
 
+TEST(ParsePriorityFieldValueTest, ParsePriorityFieldValue) {
+  // Default values
+  QuicReceiveControlStream::ParsePriorityFieldValueResult result =
+      QuicReceiveControlStream::ParsePriorityFieldValue("");
+  EXPECT_TRUE(result.success);
+  EXPECT_EQ(3, result.urgency);
+  EXPECT_FALSE(result.incremental);
+
+  result = QuicReceiveControlStream::ParsePriorityFieldValue("i=?1");
+  EXPECT_TRUE(result.success);
+  EXPECT_EQ(3, result.urgency);
+  EXPECT_TRUE(result.incremental);
+
+  result = QuicReceiveControlStream::ParsePriorityFieldValue("u=5");
+  EXPECT_TRUE(result.success);
+  EXPECT_EQ(5, result.urgency);
+  EXPECT_FALSE(result.incremental);
+
+  result = QuicReceiveControlStream::ParsePriorityFieldValue("u=5, i");
+  EXPECT_TRUE(result.success);
+  EXPECT_EQ(5, result.urgency);
+  EXPECT_TRUE(result.incremental);
+
+  result = QuicReceiveControlStream::ParsePriorityFieldValue("i, u=1");
+  EXPECT_TRUE(result.success);
+  EXPECT_EQ(1, result.urgency);
+  EXPECT_TRUE(result.incremental);
+
+  // Duplicate values are allowed.
+  result =
+      QuicReceiveControlStream::ParsePriorityFieldValue("u=5, i=?1, i=?0, u=2");
+  EXPECT_TRUE(result.success);
+  EXPECT_EQ(2, result.urgency);
+  EXPECT_FALSE(result.incremental);
+
+  // Unknown parameters MUST be ignored.
+  result = QuicReceiveControlStream::ParsePriorityFieldValue("a=42, u=4, i=?0");
+  EXPECT_TRUE(result.success);
+  EXPECT_EQ(4, result.urgency);
+  EXPECT_FALSE(result.incremental);
+
+  // Out-of-range values MUST be ignored.
+  result = QuicReceiveControlStream::ParsePriorityFieldValue("u=-2, i");
+  EXPECT_TRUE(result.success);
+  EXPECT_EQ(3, result.urgency);
+  EXPECT_TRUE(result.incremental);
+
+  // Values of unexpected types MUST be ignored.
+  result =
+      QuicReceiveControlStream::ParsePriorityFieldValue("u=4.2, i=\"foo\"");
+  EXPECT_TRUE(result.success);
+  EXPECT_EQ(3, result.urgency);
+  EXPECT_FALSE(result.incremental);
+
+  // Values of the right type but different names are ignored.
+  result = QuicReceiveControlStream::ParsePriorityFieldValue("a=4, b=?1");
+  EXPECT_TRUE(result.success);
+  EXPECT_EQ(3, result.urgency);
+  EXPECT_FALSE(result.incremental);
+
+  // Cannot be parsed as structured headers.
+  result = QuicReceiveControlStream::ParsePriorityFieldValue("000");
+  EXPECT_FALSE(result.success);
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quiche/quic/core/http/quic_spdy_session.cc b/quiche/quic/core/http/quic_spdy_session.cc
index 66e5b8e..e9effda 100644
--- a/quiche/quic/core/http/quic_spdy_session.cc
+++ b/quiche/quic/core/http/quic_spdy_session.cc
@@ -377,10 +377,7 @@
   }
 
   void OnPriorityUpdate(SpdyStreamId /*prioritized_stream_id*/,
-                        absl::string_view /*priority_field_value*/) override {
-    // TODO(b/171470299): Parse and call
-    // QuicSpdySession::OnPriorityUpdateForRequestStream().
-  }
+                        absl::string_view /*priority_field_value*/) override {}
 
   bool OnUnknownFrame(SpdyStreamId /*stream_id*/,
                       uint8_t /*frame_type*/) override {
diff --git a/quiche/quic/core/http/quic_spdy_session_test.cc b/quiche/quic/core/http/quic_spdy_session_test.cc
index aa7ba10..81a8999 100644
--- a/quiche/quic/core/http/quic_spdy_session_test.cc
+++ b/quiche/quic/core/http/quic_spdy_session_test.cc
@@ -2244,6 +2244,108 @@
   EXPECT_EQ(2u, stream2->precedence().spdy3_priority());
 }
 
+TEST_P(QuicSpdySessionTestServer, OnInvalidPriorityUpdateFrame) {
+  if (!VersionUsesHttp3(transport_version())) {
+    return;
+  }
+
+  StrictMock<MockHttp3DebugVisitor> debug_visitor;
+  session_.set_debug_visitor(&debug_visitor);
+
+  // Create control stream.
+  QuicStreamId receive_control_stream_id =
+      GetNthClientInitiatedUnidirectionalStreamId(transport_version(), 3);
+  char type[] = {kControlStream};
+  absl::string_view stream_type(type, 1);
+  QuicStreamOffset offset = 0;
+  QuicStreamFrame data1(receive_control_stream_id, false, offset, stream_type);
+  offset += stream_type.length();
+  EXPECT_CALL(debug_visitor,
+              OnPeerControlStreamCreated(receive_control_stream_id));
+  session_.OnStreamFrame(data1);
+  EXPECT_EQ(receive_control_stream_id,
+            QuicSpdySessionPeer::GetReceiveControlStream(&session_)->id());
+
+  // Send SETTINGS frame.
+  std::string serialized_settings = HttpEncoder::SerializeSettingsFrame({});
+  QuicStreamFrame data2(receive_control_stream_id, false, offset,
+                        serialized_settings);
+  offset += serialized_settings.length();
+  EXPECT_CALL(debug_visitor, OnSettingsFrameReceived(_));
+  session_.OnStreamFrame(data2);
+
+  // PRIORITY_UPDATE frame with Priority Field Value that is not valid
+  // Structured Headers.
+  const QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0);
+  PriorityUpdateFrame priority_update{stream_id, "00"};
+
+  EXPECT_CALL(debug_visitor, OnPriorityUpdateFrameReceived(priority_update));
+  if (GetQuicReloadableFlag(quic_priority_update_structured_headers_parser)) {
+    EXPECT_CALL(*connection_,
+                CloseConnection(QUIC_INVALID_PRIORITY_UPDATE,
+                                "Invalid PRIORITY_UPDATE frame payload.", _));
+  } else {
+    EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
+  }
+
+  std::string serialized_priority_update =
+      HttpEncoder::SerializePriorityUpdateFrame(priority_update);
+  QuicStreamFrame data3(receive_control_stream_id,
+                        /* fin = */ false, offset, serialized_priority_update);
+  session_.OnStreamFrame(data3);
+}
+
+TEST_P(QuicSpdySessionTestServer, OnPriorityUpdateFrameOutOfBoundsUrgency) {
+  if (!VersionUsesHttp3(transport_version())) {
+    return;
+  }
+
+  StrictMock<MockHttp3DebugVisitor> debug_visitor;
+  session_.set_debug_visitor(&debug_visitor);
+
+  // Create control stream.
+  QuicStreamId receive_control_stream_id =
+      GetNthClientInitiatedUnidirectionalStreamId(transport_version(), 3);
+  char type[] = {kControlStream};
+  absl::string_view stream_type(type, 1);
+  QuicStreamOffset offset = 0;
+  QuicStreamFrame data1(receive_control_stream_id, false, offset, stream_type);
+  offset += stream_type.length();
+  EXPECT_CALL(debug_visitor,
+              OnPeerControlStreamCreated(receive_control_stream_id));
+  session_.OnStreamFrame(data1);
+  EXPECT_EQ(receive_control_stream_id,
+            QuicSpdySessionPeer::GetReceiveControlStream(&session_)->id());
+
+  // Send SETTINGS frame.
+  std::string serialized_settings = HttpEncoder::SerializeSettingsFrame({});
+  QuicStreamFrame data2(receive_control_stream_id, false, offset,
+                        serialized_settings);
+  offset += serialized_settings.length();
+  EXPECT_CALL(debug_visitor, OnSettingsFrameReceived(_));
+  session_.OnStreamFrame(data2);
+
+  // PRIORITY_UPDATE frame with urgency not in [0,7].
+  const QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0);
+  PriorityUpdateFrame priority_update{stream_id, "u=9"};
+
+  EXPECT_CALL(debug_visitor, OnPriorityUpdateFrameReceived(priority_update));
+  if (GetQuicReloadableFlag(quic_priority_update_structured_headers_parser)) {
+    EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
+  } else {
+    EXPECT_CALL(*connection_,
+                CloseConnection(
+                    QUIC_INVALID_PRIORITY_UPDATE,
+                    "Invalid value for PRIORITY_UPDATE urgency parameter.", _));
+  }
+
+  std::string serialized_priority_update =
+      HttpEncoder::SerializePriorityUpdateFrame(priority_update);
+  QuicStreamFrame data3(receive_control_stream_id,
+                        /* fin = */ false, offset, serialized_priority_update);
+  session_.OnStreamFrame(data3);
+}
+
 TEST_P(QuicSpdySessionTestServer, SimplePendingStreamType) {
   if (!VersionUsesHttp3(transport_version())) {
     return;
diff --git a/quiche/quic/core/quic_flags_list.h b/quiche/quic/core/quic_flags_list.h
index d88f628..0b148a1 100644
--- a/quiche/quic/core/quic_flags_list.h
+++ b/quiche/quic/core/quic_flags_list.h
@@ -87,6 +87,8 @@
 QUIC_FLAG(quic_reloadable_flag_quic_connection_migration_use_new_cid_v2, true)
 // If true, use next_connection_id_sequence_number to validate retired cid number.
 QUIC_FLAG(quic_reloadable_flag_quic_check_retire_cid_with_next_cid_sequence_number, true)
+// If true, use quiche/common/structured_headers in QuicReceiveControlStream::OnPriorityUpdateFrame().
+QUIC_FLAG(quic_reloadable_flag_quic_priority_update_structured_headers_parser, false)
 // If true, uses conservative cwnd gain and pacing gain when cwnd gets bootstrapped.
 QUIC_FLAG(quic_reloadable_flag_quic_conservative_cwnd_and_pacing_gains, false)
 // Instead of assuming an incoming connection ID length for short headers, ask each time.