Use structured headers serializer for PRIORITY_UPDATE frames.

Use QUICHE's structured headers library for serialization, and add tests.
Does not change the output format from the hardcoded StrCat that is being
replaced, but adds capability to serialize incremental bit for future use.

PiperOrigin-RevId: 491343647
diff --git a/quiche/quic/core/http/quic_send_control_stream.cc b/quiche/quic/core/http/quic_send_control_stream.cc
index 97b27b2..fbde32c 100644
--- a/quiche/quic/core/http/quic_send_control_stream.cc
+++ b/quiche/quic/core/http/quic_send_control_stream.cc
@@ -16,8 +16,19 @@
 #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,
                                              QuicSpdySession* spdy_session,
@@ -82,19 +93,23 @@
                     nullptr);
 }
 
-void QuicSendControlStream::WritePriorityUpdate(
-    const PriorityUpdateFrame& priority_update) {
+void QuicSendControlStream::WritePriorityUpdate(QuicStreamId stream_id,
+                                                int urgency, bool incremental) {
   QuicConnection::ScopedPacketFlusher flusher(session()->connection());
   MaybeSendSettingsFrame();
 
+  const std::string priority_field_value =
+      SerializePriorityFieldValue(urgency, incremental);
+  PriorityUpdateFrame priority_update_frame{stream_id, priority_field_value};
   if (spdy_session_->debug_visitor()) {
-    spdy_session_->debug_visitor()->OnPriorityUpdateFrameSent(priority_update);
+    spdy_session_->debug_visitor()->OnPriorityUpdateFrameSent(
+        priority_update_frame);
   }
 
   std::string frame =
-      HttpEncoder::SerializePriorityUpdateFrame(priority_update);
+      HttpEncoder::SerializePriorityUpdateFrame(priority_update_frame);
   QUIC_DVLOG(1) << "Control Stream " << id() << " is writing "
-                << priority_update;
+                << priority_update_frame;
   WriteOrBufferData(frame, false, nullptr);
 }
 
@@ -111,4 +126,30 @@
   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 18ee01f..c76aaf9 100644
--- a/quiche/quic/core/http/quic_send_control_stream.h
+++ b/quiche/quic/core/http/quic_send_control_stream.h
@@ -39,7 +39,8 @@
 
   // Send a PRIORITY_UPDATE frame on this stream, and a SETTINGS frame
   // beforehand if one has not been already sent.
-  void WritePriorityUpdate(const PriorityUpdateFrame& priority_update);
+  void WritePriorityUpdate(QuicStreamId stream_id, int urgency,
+                           bool incremental);
 
   // Send a GOAWAY frame on this stream, and a SETTINGS frame beforehand if one
   // has not been already sent.
@@ -49,6 +50,9 @@
   // 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 b606934..1627e78 100644
--- a/quiche/quic/core/http/quic_send_control_stream_test.cc
+++ b/quiche/quic/core/http/quic_send_control_stream_test.cc
@@ -251,11 +251,14 @@
   // SETTINGS frame, and a greased frame before the PRIORITY_UPDATE frame.
   EXPECT_CALL(session_, WritevData(send_control_stream_->id(), _, _, _, _, _))
       .Times(4);
-  PriorityUpdateFrame frame;
-  send_control_stream_->WritePriorityUpdate(frame);
+  send_control_stream_->WritePriorityUpdate(
+      /* stream_id = */ 0, /* urgency = */ 3, /* incremental = */ false);
+
+  EXPECT_TRUE(testing::Mock::VerifyAndClearExpectations(&session_));
 
   EXPECT_CALL(session_, WritevData(send_control_stream_->id(), _, _, _, _, _));
-  send_control_stream_->WritePriorityUpdate(frame);
+  send_control_stream_->WritePriorityUpdate(
+      /* stream_id = */ 0, /* urgency = */ 3, /* incremental = */ false);
 }
 
 TEST_P(QuicSendControlStreamTest, CloseControlStream) {
@@ -291,6 +294,23 @@
   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 e9effda..8827e01 100644
--- a/quiche/quic/core/http/quic_spdy_session.cc
+++ b/quiche/quic/core/http/quic_spdy_session.cc
@@ -694,22 +694,22 @@
       /* exclusive = */ false, std::move(ack_listener));
 }
 
-size_t QuicSpdySession::WritePriority(QuicStreamId id,
+size_t QuicSpdySession::WritePriority(QuicStreamId stream_id,
                                       QuicStreamId parent_stream_id, int weight,
                                       bool exclusive) {
   QUICHE_DCHECK(!VersionUsesHttp3(transport_version()));
-  SpdyPriorityIR priority_frame(id, parent_stream_id, weight, exclusive);
+  SpdyPriorityIR priority_frame(stream_id, parent_stream_id, weight, exclusive);
   SpdySerializedFrame frame(spdy_framer_.SerializeFrame(priority_frame));
   headers_stream()->WriteOrBufferData(
       absl::string_view(frame.data(), frame.size()), false, nullptr);
   return frame.size();
 }
 
-void QuicSpdySession::WriteHttp3PriorityUpdate(
-    const PriorityUpdateFrame& priority_update) {
+void QuicSpdySession::WriteHttp3PriorityUpdate(QuicStreamId stream_id,
+                                               int urgency, bool incremental) {
   QUICHE_DCHECK(VersionUsesHttp3(transport_version()));
 
-  send_control_stream_->WritePriorityUpdate(priority_update);
+  send_control_stream_->WritePriorityUpdate(stream_id, urgency, incremental);
 }
 
 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 476841d..0cb3f6b 100644
--- a/quiche/quic/core/http/quic_spdy_session.h
+++ b/quiche/quic/core/http/quic_spdy_session.h
@@ -209,11 +209,12 @@
 
   // Writes an HTTP/2 PRIORITY frame the to peer. Returns the size in bytes of
   // the resulting PRIORITY frame.
-  size_t WritePriority(QuicStreamId id, QuicStreamId parent_stream_id,
+  size_t WritePriority(QuicStreamId stream_id, QuicStreamId parent_stream_id,
                        int weight, bool exclusive);
 
   // Writes an HTTP/3 PRIORITY_UPDATE frame to the peer.
-  void WriteHttp3PriorityUpdate(const PriorityUpdateFrame& priority_update);
+  void WriteHttp3PriorityUpdate(QuicStreamId stream_id, int urgency,
+                                bool incremental);
 
   // 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_stream.cc b/quiche/quic/core/http/quic_spdy_stream.cc
index ed0dd74..b1f9db5 100644
--- a/quiche/quic/core/http/quic_spdy_stream.cc
+++ b/quiche/quic/core/http/quic_spdy_stream.cc
@@ -588,14 +588,14 @@
   }
 
   // Value between 0 and 7, inclusive.  Lower value means higher priority.
-  int urgency = precedence().spdy3_priority();
+  const int urgency = precedence().spdy3_priority();
   if (last_sent_urgency_ == urgency) {
     return;
   }
   last_sent_urgency_ = urgency;
 
-  PriorityUpdateFrame priority_update{id(), absl::StrCat("u=", urgency)};
-  spdy_session_->WriteHttp3PriorityUpdate(priority_update);
+  spdy_session_->WriteHttp3PriorityUpdate(id(), urgency,
+                                          /* incremental = */ false);
 }
 
 void QuicSpdyStream::OnHeadersTooLarge() { Reset(QUIC_HEADERS_TOO_LARGE); }