QuicStreamPriority refactor

Create QuicStreamPriority class in a dedicated build target, move parsing and
serializing functions and their tests together.  Consolidate constants like
minimum, maximum and default values, and structured header dictionary keys "u"
and "i", and re-use them in both the parsing and serializing methods (previously
ParsePriorityFieldValue() had these values hardcoded).

https://github.com/google/quiche/issues/25

PiperOrigin-RevId: 494755193
diff --git a/build/source_list.bzl b/build/source_list.bzl
index 4d627c1..76eb89b 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -328,6 +328,7 @@
     "quic/core/quic_stream.h",
     "quic/core/quic_stream_frame_data_producer.h",
     "quic/core/quic_stream_id_manager.h",
+    "quic/core/quic_stream_priority.h",
     "quic/core/quic_stream_send_buffer.h",
     "quic/core/quic_stream_sequencer.h",
     "quic/core/quic_stream_sequencer_buffer.h",
@@ -650,6 +651,7 @@
     "quic/core/quic_socket_address_coder.cc",
     "quic/core/quic_stream.cc",
     "quic/core/quic_stream_id_manager.cc",
+    "quic/core/quic_stream_priority.cc",
     "quic/core/quic_stream_send_buffer.cc",
     "quic/core/quic_stream_sequencer.cc",
     "quic/core/quic_stream_sequencer_buffer.cc",
@@ -1226,6 +1228,7 @@
     "quic/core/quic_session_test.cc",
     "quic/core/quic_socket_address_coder_test.cc",
     "quic/core/quic_stream_id_manager_test.cc",
+    "quic/core/quic_stream_priority_test.cc",
     "quic/core/quic_stream_send_buffer_test.cc",
     "quic/core/quic_stream_sequencer_buffer_test.cc",
     "quic/core/quic_stream_sequencer_test.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index 82132ae..78333b6 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -328,6 +328,7 @@
     "src/quiche/quic/core/quic_stream.h",
     "src/quiche/quic/core/quic_stream_frame_data_producer.h",
     "src/quiche/quic/core/quic_stream_id_manager.h",
+    "src/quiche/quic/core/quic_stream_priority.h",
     "src/quiche/quic/core/quic_stream_send_buffer.h",
     "src/quiche/quic/core/quic_stream_sequencer.h",
     "src/quiche/quic/core/quic_stream_sequencer_buffer.h",
@@ -650,6 +651,7 @@
     "src/quiche/quic/core/quic_socket_address_coder.cc",
     "src/quiche/quic/core/quic_stream.cc",
     "src/quiche/quic/core/quic_stream_id_manager.cc",
+    "src/quiche/quic/core/quic_stream_priority.cc",
     "src/quiche/quic/core/quic_stream_send_buffer.cc",
     "src/quiche/quic/core/quic_stream_sequencer.cc",
     "src/quiche/quic/core/quic_stream_sequencer_buffer.cc",
@@ -1226,6 +1228,7 @@
     "src/quiche/quic/core/quic_session_test.cc",
     "src/quiche/quic/core/quic_socket_address_coder_test.cc",
     "src/quiche/quic/core/quic_stream_id_manager_test.cc",
+    "src/quiche/quic/core/quic_stream_priority_test.cc",
     "src/quiche/quic/core/quic_stream_send_buffer_test.cc",
     "src/quiche/quic/core/quic_stream_sequencer_buffer_test.cc",
     "src/quiche/quic/core/quic_stream_sequencer_test.cc",
diff --git a/build/source_list.json b/build/source_list.json
index 3ded441..3811e8b 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -327,6 +327,7 @@
     "quiche/quic/core/quic_stream.h",
     "quiche/quic/core/quic_stream_frame_data_producer.h",
     "quiche/quic/core/quic_stream_id_manager.h",
+    "quiche/quic/core/quic_stream_priority.h",
     "quiche/quic/core/quic_stream_send_buffer.h",
     "quiche/quic/core/quic_stream_sequencer.h",
     "quiche/quic/core/quic_stream_sequencer_buffer.h",
@@ -649,6 +650,7 @@
     "quiche/quic/core/quic_socket_address_coder.cc",
     "quiche/quic/core/quic_stream.cc",
     "quiche/quic/core/quic_stream_id_manager.cc",
+    "quiche/quic/core/quic_stream_priority.cc",
     "quiche/quic/core/quic_stream_send_buffer.cc",
     "quiche/quic/core/quic_stream_sequencer.cc",
     "quiche/quic/core/quic_stream_sequencer_buffer.cc",
@@ -1225,6 +1227,7 @@
     "quiche/quic/core/quic_session_test.cc",
     "quiche/quic/core/quic_socket_address_coder_test.cc",
     "quiche/quic/core/quic_stream_id_manager_test.cc",
+    "quiche/quic/core/quic_stream_priority_test.cc",
     "quiche/quic/core/quic_stream_send_buffer_test.cc",
     "quiche/quic/core/quic_stream_sequencer_buffer_test.cc",
     "quiche/quic/core/quic_stream_sequencer_test.cc",
diff --git a/quiche/quic/core/http/quic_receive_control_stream.cc b/quiche/quic/core/http/quic_receive_control_stream.cc
index a35a925..b2b56d8 100644
--- a/quiche/quic/core/http/quic_receive_control_stream.cc
+++ b/quiche/quic/core/http/quic_receive_control_stream.cc
@@ -12,11 +12,11 @@
 #include "quiche/quic/core/http/http_constants.h"
 #include "quiche/quic/core/http/http_decoder.h"
 #include "quiche/quic/core/http/quic_spdy_session.h"
+#include "quiche/quic/core/quic_stream_priority.h"
 #include "quiche/quic/core/quic_types.h"
 #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 {
 
@@ -149,8 +149,8 @@
     }
 
     const QuicStreamId stream_id = frame.prioritized_element_id;
-    return spdy_session_->OnPriorityUpdateForRequestStream(stream_id,
-                                                           result.urgency);
+    return spdy_session_->OnPriorityUpdateForRequestStream(
+        stream_id, result.priority.urgency);
   }
 
   for (absl::string_view key_value :
@@ -225,45 +225,6 @@
   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 e5eeb01..71d0bf2 100644
--- a/quiche/quic/core/http/quic_receive_control_stream.h
+++ b/quiche/quic/core/http/quic_receive_control_stream.h
@@ -20,13 +20,6 @@
     : 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;
@@ -67,10 +60,6 @@
 
   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 9181ba6..af7e5d4 100644
--- a/quiche/quic/core/http/quic_receive_control_stream_test.cc
+++ b/quiche/quic/core/http/quic_receive_control_stream_test.cc
@@ -456,71 +456,6 @@
                       /* 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_send_control_stream.cc b/quiche/quic/core/http/quic_send_control_stream.cc
index fbde32c..d3c2ef2 100644
--- a/quiche/quic/core/http/quic_send_control_stream.cc
+++ b/quiche/quic/core/http/quic_send_control_stream.cc
@@ -16,18 +16,10 @@
 #include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/core/quic_utils.h"
 #include "quiche/quic/platform/api/quic_logging.h"
-#include "quiche/common/structured_headers.h"
 
 namespace quic {
 namespace {
 
-// See https://httpwg.org/specs/rfc9218.html for Priority Field Value format.
-constexpr absl::string_view kUrgencyKey = "u";
-constexpr absl::string_view kIncrementalKey = "i";
-constexpr int kMinimumUrgency = 0;
-constexpr int kMaximumUrgency = 7;
-constexpr bool kDefaultIncremental = false;
-
 }  // anonymous namespace
 
 QuicSendControlStream::QuicSendControlStream(QuicStreamId id,
@@ -94,12 +86,12 @@
 }
 
 void QuicSendControlStream::WritePriorityUpdate(QuicStreamId stream_id,
-                                                int urgency, bool incremental) {
+                                                QuicStreamPriority priority) {
   QuicConnection::ScopedPacketFlusher flusher(session()->connection());
   MaybeSendSettingsFrame();
 
   const std::string priority_field_value =
-      SerializePriorityFieldValue(urgency, incremental);
+      SerializePriorityFieldValue(priority);
   PriorityUpdateFrame priority_update_frame{stream_id, priority_field_value};
   if (spdy_session_->debug_visitor()) {
     spdy_session_->debug_visitor()->OnPriorityUpdateFrameSent(
@@ -126,30 +118,4 @@
   WriteOrBufferData(HttpEncoder::SerializeGoAwayFrame(frame), false, nullptr);
 }
 
-std::string QuicSendControlStream::SerializePriorityFieldValue(
-    int urgency, bool incremental) {
-  quiche::structured_headers::Dictionary dictionary;
-
-  if (urgency != QuicStream::kDefaultUrgency && urgency >= kMinimumUrgency &&
-      urgency <= kMaximumUrgency) {
-    dictionary[kUrgencyKey] = quiche::structured_headers::ParameterizedMember(
-        quiche::structured_headers::Item(static_cast<int64_t>(urgency)), {});
-  }
-
-  if (incremental != kDefaultIncremental) {
-    dictionary[kIncrementalKey] =
-        quiche::structured_headers::ParameterizedMember(
-            quiche::structured_headers::Item(incremental), {});
-  }
-
-  absl::optional<std::string> priority_field_value =
-      quiche::structured_headers::SerializeDictionary(dictionary);
-  if (!priority_field_value.has_value()) {
-    QUICHE_BUG(priority_field_value_serialization_failed);
-    return "";
-  }
-
-  return *priority_field_value;
-}
-
 }  // namespace quic
diff --git a/quiche/quic/core/http/quic_send_control_stream.h b/quiche/quic/core/http/quic_send_control_stream.h
index c76aaf9..6d0745c 100644
--- a/quiche/quic/core/http/quic_send_control_stream.h
+++ b/quiche/quic/core/http/quic_send_control_stream.h
@@ -7,6 +7,7 @@
 
 #include "quiche/quic/core/http/http_encoder.h"
 #include "quiche/quic/core/quic_stream.h"
+#include "quiche/quic/core/quic_stream_priority.h"
 #include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/platform/api/quic_export.h"
 #include "quiche/quic/platform/api/quic_logging.h"
@@ -39,8 +40,7 @@
 
   // Send a PRIORITY_UPDATE frame on this stream, and a SETTINGS frame
   // beforehand if one has not been already sent.
-  void WritePriorityUpdate(QuicStreamId stream_id, int urgency,
-                           bool incremental);
+  void WritePriorityUpdate(QuicStreamId stream_id, QuicStreamPriority priority);
 
   // Send a GOAWAY frame on this stream, and a SETTINGS frame beforehand if one
   // has not been already sent.
@@ -50,9 +50,6 @@
   // never be called.
   void OnDataAvailable() override { QUICHE_NOTREACHED(); }
 
-  // Serialize the Priority Field Value for a PRIORITY_UPDATE frame.
-  static std::string SerializePriorityFieldValue(int urgency, bool incremental);
-
  private:
   // Track if a settings frame is already sent.
   bool settings_sent_;
diff --git a/quiche/quic/core/http/quic_send_control_stream_test.cc b/quiche/quic/core/http/quic_send_control_stream_test.cc
index 1627e78..20e96c6 100644
--- a/quiche/quic/core/http/quic_send_control_stream_test.cc
+++ b/quiche/quic/core/http/quic_send_control_stream_test.cc
@@ -252,13 +252,15 @@
   EXPECT_CALL(session_, WritevData(send_control_stream_->id(), _, _, _, _, _))
       .Times(4);
   send_control_stream_->WritePriorityUpdate(
-      /* stream_id = */ 0, /* urgency = */ 3, /* incremental = */ false);
+      /* stream_id = */ 0,
+      QuicStreamPriority{/* urgency = */ 3, /* incremental = */ false});
 
   EXPECT_TRUE(testing::Mock::VerifyAndClearExpectations(&session_));
 
   EXPECT_CALL(session_, WritevData(send_control_stream_->id(), _, _, _, _, _));
   send_control_stream_->WritePriorityUpdate(
-      /* stream_id = */ 0, /* urgency = */ 3, /* incremental = */ false);
+      /* stream_id = */ 0,
+      QuicStreamPriority{/* urgency = */ 3, /* incremental = */ false});
 }
 
 TEST_P(QuicSendControlStreamTest, CloseControlStream) {
@@ -294,23 +296,6 @@
   send_control_stream_->SendGoAway(stream_id);
 }
 
-TEST(SerializePriorityFieldValueTest, SerializePriorityFieldValue) {
-  // Default value is omitted.
-  EXPECT_EQ("", QuicSendControlStream::SerializePriorityFieldValue(
-                    /* urgency = */ 3, /* incremental = */ false));
-  EXPECT_EQ("u=5", QuicSendControlStream::SerializePriorityFieldValue(
-                       /* urgency = */ 5, /* incremental = */ false));
-  EXPECT_EQ("i", QuicSendControlStream::SerializePriorityFieldValue(
-                     /* urgency = */ 3, /* incremental = */ true));
-  EXPECT_EQ("u=0, i", QuicSendControlStream::SerializePriorityFieldValue(
-                          /* urgency = */ 0, /* incremental = */ true));
-  // Out-of-bound value is ignored.
-  EXPECT_EQ("", QuicSendControlStream::SerializePriorityFieldValue(
-                    /* urgency = */ -2, /* incremental = */ false));
-  EXPECT_EQ("i", QuicSendControlStream::SerializePriorityFieldValue(
-                     /* urgency = */ 9, /* incremental = */ true));
-}
-
 }  // 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 8827e01..ae83de6 100644
--- a/quiche/quic/core/http/quic_spdy_session.cc
+++ b/quiche/quic/core/http/quic_spdy_session.cc
@@ -706,10 +706,10 @@
 }
 
 void QuicSpdySession::WriteHttp3PriorityUpdate(QuicStreamId stream_id,
-                                               int urgency, bool incremental) {
+                                               QuicStreamPriority priority) {
   QUICHE_DCHECK(VersionUsesHttp3(transport_version()));
 
-  send_control_stream_->WritePriorityUpdate(stream_id, urgency, incremental);
+  send_control_stream_->WritePriorityUpdate(stream_id, priority);
 }
 
 void QuicSpdySession::OnHttp3GoAway(uint64_t id) {
diff --git a/quiche/quic/core/http/quic_spdy_session.h b/quiche/quic/core/http/quic_spdy_session.h
index 0cb3f6b..60ff11a 100644
--- a/quiche/quic/core/http/quic_spdy_session.h
+++ b/quiche/quic/core/http/quic_spdy_session.h
@@ -26,6 +26,7 @@
 #include "quiche/quic/core/qpack/qpack_receive_stream.h"
 #include "quiche/quic/core/qpack/qpack_send_stream.h"
 #include "quiche/quic/core/quic_session.h"
+#include "quiche/quic/core/quic_stream_priority.h"
 #include "quiche/quic/core/quic_time.h"
 #include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/core/quic_versions.h"
@@ -213,8 +214,8 @@
                        int weight, bool exclusive);
 
   // Writes an HTTP/3 PRIORITY_UPDATE frame to the peer.
-  void WriteHttp3PriorityUpdate(QuicStreamId stream_id, int urgency,
-                                bool incremental);
+  void WriteHttp3PriorityUpdate(QuicStreamId stream_id,
+                                QuicStreamPriority priority);
 
   // Process received HTTP/3 GOAWAY frame.  When sent from server to client,
   // |id| is a stream ID.  When sent from client to server, |id| is a push ID.
diff --git a/quiche/quic/core/http/quic_spdy_session_test.cc b/quiche/quic/core/http/quic_spdy_session_test.cc
index a90e276..7410728 100644
--- a/quiche/quic/core/http/quic_spdy_session_test.cc
+++ b/quiche/quic/core/http/quic_spdy_session_test.cc
@@ -29,6 +29,7 @@
 #include "quiche/quic/core/quic_error_codes.h"
 #include "quiche/quic/core/quic_packets.h"
 #include "quiche/quic/core/quic_stream.h"
+#include "quiche/quic/core/quic_stream_priority.h"
 #include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/core/quic_utils.h"
 #include "quiche/quic/core/quic_versions.h"
@@ -2219,7 +2220,7 @@
 
   // PRIORITY_UPDATE frame arrives after stream creation.
   TestStream* stream1 = session_.CreateIncomingStream(stream_id1);
-  EXPECT_EQ(QuicStream::kDefaultUrgency,
+  EXPECT_EQ(QuicStreamPriority::kDefaultUrgency,
             stream1->precedence().spdy3_priority());
   EXPECT_CALL(debug_visitor, OnPriorityUpdateFrameReceived(priority_update1));
   session_.OnStreamFrame(data3);
diff --git a/quiche/quic/core/http/quic_spdy_stream.cc b/quiche/quic/core/http/quic_spdy_stream.cc
index b1f9db5..4cb89a8 100644
--- a/quiche/quic/core/http/quic_spdy_stream.cc
+++ b/quiche/quic/core/http/quic_spdy_stream.cc
@@ -190,8 +190,7 @@
                HttpDecoderOptionsForBidiStream(spdy_session)),
       sequencer_offset_(0),
       is_decoder_processing_input_(false),
-      ack_listener_(nullptr),
-      last_sent_urgency_(kDefaultUrgency) {
+      ack_listener_(nullptr) {
   QUICHE_DCHECK_EQ(session()->connection(), spdy_session->connection());
   QUICHE_DCHECK_EQ(transport_version(), spdy_session->transport_version());
   QUICHE_DCHECK(!QuicUtils::IsCryptoStreamId(transport_version(), id));
@@ -225,8 +224,7 @@
       decoder_(http_decoder_visitor_.get()),
       sequencer_offset_(sequencer()->NumBytesConsumed()),
       is_decoder_processing_input_(false),
-      ack_listener_(nullptr),
-      last_sent_urgency_(kDefaultUrgency) {
+      ack_listener_(nullptr) {
   QUICHE_DCHECK_EQ(session()->connection(), spdy_session->connection());
   QUICHE_DCHECK_EQ(transport_version(), spdy_session->transport_version());
   QUICHE_DCHECK(!QuicUtils::IsCryptoStreamId(transport_version(), id()));
@@ -588,14 +586,15 @@
   }
 
   // Value between 0 and 7, inclusive.  Lower value means higher priority.
-  const int urgency = precedence().spdy3_priority();
-  if (last_sent_urgency_ == urgency) {
+  const uint8_t urgency = precedence().spdy3_priority();
+  const QuicStreamPriority priority{urgency,
+                                    QuicStreamPriority::kDefaultIncremental};
+  if (last_sent_priority_ == priority) {
     return;
   }
-  last_sent_urgency_ = urgency;
+  last_sent_priority_ = priority;
 
-  spdy_session_->WriteHttp3PriorityUpdate(id(), urgency,
-                                          /* incremental = */ false);
+  spdy_session_->WriteHttp3PriorityUpdate(id(), priority);
 }
 
 void QuicSpdyStream::OnHeadersTooLarge() { Reset(QUIC_HEADERS_TOO_LARGE); }
diff --git a/quiche/quic/core/http/quic_spdy_stream.h b/quiche/quic/core/http/quic_spdy_stream.h
index a4ca50e..450e154 100644
--- a/quiche/quic/core/http/quic_spdy_stream.h
+++ b/quiche/quic/core/http/quic_spdy_stream.h
@@ -29,6 +29,7 @@
 #include "quiche/quic/core/quic_error_codes.h"
 #include "quiche/quic/core/quic_packets.h"
 #include "quiche/quic/core/quic_stream.h"
+#include "quiche/quic/core/quic_stream_priority.h"
 #include "quiche/quic/core/quic_stream_sequencer.h"
 #include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/core/web_transport_interface.h"
@@ -225,8 +226,8 @@
 
   QuicSpdySession* spdy_session() const { return spdy_session_; }
 
-  // Send PRIORITY_UPDATE frame and update |last_sent_urgency_| if
-  // |last_sent_urgency_| is different from current priority.
+  // Send PRIORITY_UPDATE frame and update |last_sent_priority_| if
+  // |last_sent_priority_| is different from current priority.
   void MaybeSendPriorityUpdateFrame() override;
 
   // Returns the WebTransport session owned by this stream, if one exists.
@@ -470,9 +471,9 @@
   // Offset of unacked frame headers.
   QuicIntervalSet<QuicStreamOffset> unacked_frame_headers_offsets_;
 
-  // Urgency value sent in the last PRIORITY_UPDATE frame, or default urgency
-  // defined by the spec if no PRIORITY_UPDATE frame has been sent.
-  int last_sent_urgency_;
+  // Priority parameters sent in the last PRIORITY_UPDATE frame, or default
+  // values defined by RFC9218 if no PRIORITY_UPDATE frame has been sent.
+  QuicStreamPriority last_sent_priority_;
 
   // If this stream is a WebTransport extended CONNECT stream, contains the
   // WebTransport session associated with this stream.
diff --git a/quiche/quic/core/quic_stream.cc b/quiche/quic/core/quic_stream.cc
index 29a6d04..9a121cb 100644
--- a/quiche/quic/core/quic_stream.cc
+++ b/quiche/quic/core/quic_stream.cc
@@ -13,6 +13,7 @@
 #include "quiche/quic/core/quic_error_codes.h"
 #include "quiche/quic/core/quic_flow_controller.h"
 #include "quiche/quic/core/quic_session.h"
+#include "quiche/quic/core/quic_stream_priority.h"
 #include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/core/quic_utils.h"
 #include "quiche/quic/core/quic_versions.h"
@@ -111,9 +112,6 @@
 // static
 const SpdyPriority QuicStream::kDefaultPriority;
 
-// static
-const int QuicStream::kDefaultUrgency;
-
 PendingStream::PendingStream(QuicStreamId id, QuicSession* session)
     : id_(id),
       version_(session->version()),
@@ -1431,7 +1429,7 @@
     const QuicSession* session) {
   return spdy::SpdyStreamPrecedence(
       VersionUsesHttp3(session->transport_version())
-          ? kDefaultUrgency
+          ? QuicStreamPriority::kDefaultUrgency
           : QuicStream::kDefaultPriority);
 }
 
diff --git a/quiche/quic/core/quic_stream.h b/quiche/quic/core/quic_stream.h
index 39c895d..f8aff9a 100644
--- a/quiche/quic/core/quic_stream.h
+++ b/quiche/quic/core/quic_stream.h
@@ -161,10 +161,6 @@
 
   virtual ~QuicStream();
 
-  // Default priority for IETF QUIC, defined by the priority extension at
-  // https://httpwg.org/http-extensions/draft-ietf-httpbis-priority.html#urgency.
-  static const int kDefaultUrgency = 3;
-
   // QuicStreamSequencer::StreamInterface implementation.
   QuicStreamId id() const override { return id_; }
   ParsedQuicVersion version() const override;
diff --git a/quiche/quic/core/quic_stream_priority.cc b/quiche/quic/core/quic_stream_priority.cc
new file mode 100644
index 0000000..377866a
--- /dev/null
+++ b/quiche/quic/core/quic_stream_priority.cc
@@ -0,0 +1,80 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/quic/core/quic_stream_priority.h"
+
+#include "quiche/common/platform/api/quiche_bug_tracker.h"
+#include "quiche/common/structured_headers.h"
+
+namespace quic {
+
+std::string SerializePriorityFieldValue(QuicStreamPriority priority) {
+  quiche::structured_headers::Dictionary dictionary;
+
+  if (priority.urgency != QuicStreamPriority::kDefaultUrgency &&
+      priority.urgency >= QuicStreamPriority::kMinimumUrgency &&
+      priority.urgency <= QuicStreamPriority::kMaximumUrgency) {
+    dictionary[QuicStreamPriority::kUrgencyKey] =
+        quiche::structured_headers::ParameterizedMember(
+            quiche::structured_headers::Item(
+                static_cast<int64_t>(priority.urgency)),
+            {});
+  }
+
+  if (priority.incremental != QuicStreamPriority::kDefaultIncremental) {
+    dictionary[QuicStreamPriority::kIncrementalKey] =
+        quiche::structured_headers::ParameterizedMember(
+            quiche::structured_headers::Item(priority.incremental), {});
+  }
+
+  absl::optional<std::string> priority_field_value =
+      quiche::structured_headers::SerializeDictionary(dictionary);
+  if (!priority_field_value.has_value()) {
+    QUICHE_BUG(priority_field_value_serialization_failed);
+    return "";
+  }
+
+  return *priority_field_value;
+}
+
+ParsePriorityFieldValueResult ParsePriorityFieldValue(
+    absl::string_view priority_field_value) {
+  absl::optional<quiche::structured_headers::Dictionary> parsed_dictionary =
+      quiche::structured_headers::ParseDictionary(priority_field_value);
+  if (!parsed_dictionary.has_value()) {
+    return {false, {}};
+  }
+
+  uint8_t urgency = QuicStreamPriority::kDefaultUrgency;
+  bool incremental = QuicStreamPriority::kDefaultIncremental;
+
+  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 == QuicStreamPriority::kUrgencyKey && item.is_integer()) {
+      int parsed_urgency = item.GetInteger();
+      // Ignore out-of-range values.
+      if (parsed_urgency >= QuicStreamPriority::kMinimumUrgency &&
+          parsed_urgency <= QuicStreamPriority::kMaximumUrgency) {
+        urgency = parsed_urgency;
+      }
+    } else if (name == QuicStreamPriority::kIncrementalKey &&
+               item.is_boolean()) {
+      incremental = item.GetBoolean();
+    }
+  }
+
+  return {true, {urgency, incremental}};
+}
+
+}  // namespace quic
diff --git a/quiche/quic/core/quic_stream_priority.h b/quiche/quic/core/quic_stream_priority.h
new file mode 100644
index 0000000..33f1183
--- /dev/null
+++ b/quiche/quic/core/quic_stream_priority.h
@@ -0,0 +1,58 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_QUIC_STREAM_PRIORITY_H_
+#define QUICHE_QUIC_CORE_QUIC_STREAM_PRIORITY_H_
+
+#include <cstdint>
+#include <string>
+#include <tuple>
+
+#include "absl/strings/string_view.h"
+#include "quiche/quic/platform/api/quic_export.h"
+
+namespace quic {
+
+// Class to hold urgency and incremental values defined by
+// https://httpwg.org/specs/rfc9218.html.
+struct QUICHE_EXPORT QuicStreamPriority {
+  static constexpr int kMinimumUrgency = 0;
+  static constexpr int kMaximumUrgency = 7;
+  static constexpr int kDefaultUrgency = 3;
+  static constexpr bool kDefaultIncremental = false;
+
+  // Parameter names for Priority Field Value.
+  static constexpr absl::string_view kUrgencyKey = "u";
+  static constexpr absl::string_view kIncrementalKey = "i";
+
+  uint8_t urgency = kDefaultUrgency;
+  bool incremental = kDefaultIncremental;
+
+  bool operator==(const QuicStreamPriority& other) const {
+    return std::tie(urgency, incremental) ==
+           std::tie(other.urgency, other.incremental);
+  }
+
+  bool operator!=(const QuicStreamPriority& other) const {
+    return !operator==(other);
+  }
+};
+
+// Serializes the Priority Field Value for a PRIORITY_UPDATE frame.
+QUICHE_EXPORT std::string SerializePriorityFieldValue(
+    QuicStreamPriority priority);
+
+// Return type of ParsePriorityFieldValue().
+struct QUICHE_EXPORT ParsePriorityFieldValueResult {
+  bool success;
+  QuicStreamPriority priority;
+};
+
+// Parses the Priority Field Value field of a PRIORITY_UPDATE frame.
+QUICHE_EXPORT ParsePriorityFieldValueResult
+ParsePriorityFieldValue(absl::string_view priority_field_value);
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QUIC_STREAM_PRIORITY_H_
diff --git a/quiche/quic/core/quic_stream_priority_test.cc b/quiche/quic/core/quic_stream_priority_test.cc
new file mode 100644
index 0000000..50b6662
--- /dev/null
+++ b/quiche/quic/core/quic_stream_priority_test.cc
@@ -0,0 +1,107 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/quic/core/quic_stream_priority.h"
+
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace quic::test {
+
+TEST(QuicStreamPriority, DefaultConstructed) {
+  QuicStreamPriority priority;
+
+  EXPECT_EQ(QuicStreamPriority::kDefaultUrgency, priority.urgency);
+  EXPECT_EQ(QuicStreamPriority::kDefaultIncremental, priority.incremental);
+}
+
+TEST(QuicStreamPriority, Equals) {
+  EXPECT_EQ((QuicStreamPriority()),
+            (QuicStreamPriority{QuicStreamPriority::kDefaultUrgency,
+                                QuicStreamPriority::kDefaultIncremental}));
+  EXPECT_EQ((QuicStreamPriority{5, true}), (QuicStreamPriority{5, true}));
+  EXPECT_EQ((QuicStreamPriority{2, false}), (QuicStreamPriority{2, false}));
+
+  EXPECT_NE((QuicStreamPriority{1, true}), (QuicStreamPriority{3, true}));
+  EXPECT_NE((QuicStreamPriority{4, false}), (QuicStreamPriority{4, true}));
+  EXPECT_NE((QuicStreamPriority{6, true}), (QuicStreamPriority{2, false}));
+}
+
+TEST(SerializePriorityFieldValueTest, SerializePriorityFieldValue) {
+  // Default value is omitted.
+  EXPECT_EQ("", SerializePriorityFieldValue(
+                    {/* urgency = */ 3, /* incremental = */ false}));
+  EXPECT_EQ("u=5", SerializePriorityFieldValue(
+                       {/* urgency = */ 5, /* incremental = */ false}));
+  EXPECT_EQ("i", SerializePriorityFieldValue(
+                     {/* urgency = */ 3, /* incremental = */ true}));
+  EXPECT_EQ("u=0, i", SerializePriorityFieldValue(
+                          {/* urgency = */ 0, /* incremental = */ true}));
+  // Out-of-bound value is ignored.
+  EXPECT_EQ("i", SerializePriorityFieldValue(
+                     {/* urgency = */ 9, /* incremental = */ true}));
+}
+
+TEST(ParsePriorityFieldValueTest, ParsePriorityFieldValue) {
+  // Default values
+  ParsePriorityFieldValueResult result = ParsePriorityFieldValue("");
+  EXPECT_TRUE(result.success);
+  EXPECT_EQ(3, result.priority.urgency);
+  EXPECT_FALSE(result.priority.incremental);
+
+  result = ParsePriorityFieldValue("i=?1");
+  EXPECT_TRUE(result.success);
+  EXPECT_EQ(3, result.priority.urgency);
+  EXPECT_TRUE(result.priority.incremental);
+
+  result = ParsePriorityFieldValue("u=5");
+  EXPECT_TRUE(result.success);
+  EXPECT_EQ(5, result.priority.urgency);
+  EXPECT_FALSE(result.priority.incremental);
+
+  result = ParsePriorityFieldValue("u=5, i");
+  EXPECT_TRUE(result.success);
+  EXPECT_EQ(5, result.priority.urgency);
+  EXPECT_TRUE(result.priority.incremental);
+
+  result = ParsePriorityFieldValue("i, u=1");
+  EXPECT_TRUE(result.success);
+  EXPECT_EQ(1, result.priority.urgency);
+  EXPECT_TRUE(result.priority.incremental);
+
+  // Duplicate values are allowed.
+  result = ParsePriorityFieldValue("u=5, i=?1, i=?0, u=2");
+  EXPECT_TRUE(result.success);
+  EXPECT_EQ(2, result.priority.urgency);
+  EXPECT_FALSE(result.priority.incremental);
+
+  // Unknown parameters MUST be ignored.
+  result = ParsePriorityFieldValue("a=42, u=4, i=?0");
+  EXPECT_TRUE(result.success);
+  EXPECT_EQ(4, result.priority.urgency);
+  EXPECT_FALSE(result.priority.incremental);
+
+  // Out-of-range values MUST be ignored.
+  result = ParsePriorityFieldValue("u=-2, i");
+  EXPECT_TRUE(result.success);
+  EXPECT_EQ(3, result.priority.urgency);
+  EXPECT_TRUE(result.priority.incremental);
+
+  // Values of unexpected types MUST be ignored.
+  result = ParsePriorityFieldValue("u=4.2, i=\"foo\"");
+  EXPECT_TRUE(result.success);
+  EXPECT_EQ(3, result.priority.urgency);
+  EXPECT_FALSE(result.priority.incremental);
+
+  // Values of the right type but different names are ignored.
+  result = ParsePriorityFieldValue("a=4, b=?1");
+  EXPECT_TRUE(result.success);
+  EXPECT_EQ(3, result.priority.urgency);
+  EXPECT_FALSE(result.priority.incremental);
+
+  // Cannot be parsed as structured headers.
+  result = ParsePriorityFieldValue("000");
+  EXPECT_FALSE(result.success);
+}
+
+}  // namespace quic::test