Write PRIORITY_UPDATE frame.

gfe-relnote: n/a, change to QUIC v99-only code.  Protected by existing disabled gfe2_reloadable_flag_quic_enable_version_99.
PiperOrigin-RevId: 289127305
Change-Id: Ic515e3159362430055178010ee1c45e1a8e2c604
diff --git a/quic/core/http/quic_send_control_stream.cc b/quic/core/http/quic_send_control_stream.cc
index 2da2118..eda0729 100644
--- a/quic/core/http/quic_send_control_stream.cc
+++ b/quic/core/http/quic_send_control_stream.cc
@@ -72,6 +72,19 @@
   settings_sent_ = true;
 }
 
+void QuicSendControlStream::WritePriorityUpdate(
+    const PriorityUpdateFrame& priority_update) {
+  QuicConnection::ScopedPacketFlusher flusher(session()->connection());
+  MaybeSendSettingsFrame();
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount frame_length =
+      HttpEncoder::SerializePriorityUpdateFrame(priority_update, &buffer);
+  QUIC_DVLOG(1) << "Control Stream " << id() << " is writing "
+                << priority_update;
+  WriteOrBufferData(quiche::QuicheStringPiece(buffer.get(), frame_length),
+                    false, nullptr);
+}
+
 void QuicSendControlStream::SendMaxPushIdFrame(PushId max_push_id) {
   QuicConnection::ScopedPacketFlusher flusher(session()->connection());
 
diff --git a/quic/core/http/quic_send_control_stream.h b/quic/core/http/quic_send_control_stream.h
index f8c9515..0095317 100644
--- a/quic/core/http/quic_send_control_stream.h
+++ b/quic/core/http/quic_send_control_stream.h
@@ -38,10 +38,16 @@
   // first frame sent on this stream.
   void MaybeSendSettingsFrame();
 
-  // Construct a MAX_PUSH_ID frame and send it on this stream.
+  // Send a MAX_PUSH_ID frame on this stream, and a SETTINGS frame beforehand if
+  // one has not been already sent.
   void SendMaxPushIdFrame(PushId max_push_id);
 
-  // Serialize a GOAWAY frame from |stream_id| and send it on this stream.
+  // 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);
+
+  // Send a GOAWAY frame on this stream, and a SETTINGS frame beforehand if one
+  // has not been already sent.
   void SendGoAway(QuicStreamId stream_id);
 
   // The send control stream is write unidirectional, so this method should
diff --git a/quic/core/http/quic_send_control_stream_test.cc b/quic/core/http/quic_send_control_stream_test.cc
index 5da6448..7a03ac3 100644
--- a/quic/core/http/quic_send_control_stream_test.cc
+++ b/quic/core/http/quic_send_control_stream_test.cc
@@ -152,11 +152,26 @@
   EXPECT_CALL(session_, WritevData(send_control_stream_, _, _, _, _));
   send_control_stream_->MaybeSendSettingsFrame();
 
-  // No data should be written the sencond time MaybeSendSettingsFrame() is
+  // No data should be written the second time MaybeSendSettingsFrame() is
   // called.
   send_control_stream_->MaybeSendSettingsFrame();
 }
 
+// Send stream type and SETTINGS frame if WritePriorityUpdate() is called first.
+TEST_P(QuicSendControlStreamTest, WritePriorityBeforeSettings) {
+  Initialize();
+  testing::InSequence s;
+
+  // The first write will trigger the control stream to write stream type and a
+  // SETTINGS frame before the PRIORITY_UPDATE frame.
+  EXPECT_CALL(session_, WritevData(send_control_stream_, _, _, _, _)).Times(3);
+  PriorityUpdateFrame frame;
+  send_control_stream_->WritePriorityUpdate(frame);
+
+  EXPECT_CALL(session_, WritevData(send_control_stream_, _, _, _, _));
+  send_control_stream_->WritePriorityUpdate(frame);
+}
+
 TEST_P(QuicSendControlStreamTest, ResetControlStream) {
   Initialize();
   QuicRstStreamFrame rst_frame(kInvalidControlFrameId,
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc
index 209e46f..5df724c 100644
--- a/quic/core/http/quic_spdy_session.cc
+++ b/quic/core/http/quic_spdy_session.cc
@@ -531,6 +531,13 @@
   return frame.size();
 }
 
+void QuicSpdySession::WriteHttp3PriorityUpdate(
+    const PriorityUpdateFrame& priority_update) {
+  DCHECK(VersionUsesHttp3(transport_version()));
+
+  send_control_stream_->WritePriorityUpdate(priority_update);
+}
+
 void QuicSpdySession::OnHttp3GoAway(QuicStreamId stream_id) {
   DCHECK_EQ(perspective(), Perspective::IS_CLIENT);
   if (!QuicUtils::IsBidirectionalStreamId(stream_id) ||
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h
index b0a83e9..9f7349f 100644
--- a/quic/core/http/quic_spdy_session.h
+++ b/quic/core/http/quic_spdy_session.h
@@ -141,14 +141,17 @@
       const spdy::SpdyStreamPrecedence& precedence,
       QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
 
-  // Writes a PRIORITY frame the to peer. Returns the size in bytes of the
-  // resulting PRIORITY frame for QUIC_VERSION_43 and above. Otherwise, does
+  // Writes an HTTP/2 PRIORITY frame the to peer. Returns the size in bytes of
+  // the resulting PRIORITY frame for QUIC_VERSION_43 and above. Otherwise, does
   // nothing and returns 0.
   size_t WritePriority(QuicStreamId 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);
+
   // Process received HTTP/3 GOAWAY frame. This method should only be called on
   // the client side.
   virtual void OnHttp3GoAway(QuicStreamId stream_id);
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
index 819caef..195f92b 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -1037,7 +1037,16 @@
         std::move(ack_listener));
   }
 
-  // TODO(b/147306124): Send PRIORITY_UPDATE frame.
+  if (!priority_sent_) {
+    PriorityUpdateFrame priority_update;
+    priority_update.prioritized_element_type = REQUEST_STREAM;
+    priority_update.prioritized_element_id = id();
+    // Value between 0 and 7, inclusive.  Lower value means higher priority.
+    int urgency = precedence().spdy3_priority();
+    priority_update.priority_field_value = quiche::QuicheStrCat("u=", urgency);
+    spdy_session_->WriteHttp3PriorityUpdate(priority_update);
+    priority_sent_ = true;
+  }
 
   // Encode header list.
   QuicByteCount encoder_stream_sent_byte_count;
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc
index 1a01015..164b119 100644
--- a/quic/core/http/quic_spdy_stream_test.cc
+++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -228,19 +228,14 @@
       EXPECT_CALL(*connection_, OnCanWrite());
     }
     if (UsesHttp3()) {
-      // In this case, TestStream::WriteHeadersImpl() does not prevent writes.
-      // Six writes include priority for headers, headers frame header, headers
-      // frame, priority of trailers, trailing headers frame header, and
-      // trailers.
-      auto send_control_stream =
-          QuicSpdySessionPeer::GetSendControlStream(session_.get());
-      // The control stream will write 3 times, including stream type, settings
-      // frame and max push id, priority for headers.
+      // The control stream will write the stream type and SETTINGS frame.
       int num_control_stream_writes = 2;
       if (session_->perspective() == Perspective::IS_CLIENT) {
         // The control stream also writes the max push id frame.
         num_control_stream_writes++;
       }
+      auto send_control_stream =
+          QuicSpdySessionPeer::GetSendControlStream(session_.get());
       EXPECT_CALL(*session_, WritevData(send_control_stream,
                                         send_control_stream->id(), _, _, _))
           .Times(num_control_stream_writes);
@@ -1264,8 +1259,15 @@
 
   if (UsesHttp3()) {
     // In this case, TestStream::WriteHeadersImpl() does not prevent writes.
+    // Four writes on the request stream: HEADERS frame header and payload both
+    // for headers and trailers.
     EXPECT_CALL(*session_, WritevData(stream_, stream_->id(), _, _, _))
-        .Times(AtLeast(1));
+        .Times(4);
+    // PRIORITY_UPDATE frame on the control stream.
+    auto send_control_stream =
+        QuicSpdySessionPeer::GetSendControlStream(session_.get());
+    EXPECT_CALL(*session_, WritevData(send_control_stream,
+                                      send_control_stream->id(), _, _, _));
   }
 
   // Write the initial headers, without a FIN.
@@ -1280,6 +1282,34 @@
   EXPECT_TRUE(stream_->fin_sent());
 }
 
+TEST_P(QuicSpdyStreamTest, SendPriorityUpdate) {
+  if (!UsesHttp3()) {
+    return;
+  }
+
+  InitializeWithPerspective(kShouldProcessData, Perspective::IS_CLIENT);
+
+  // Four writes on the request stream: HEADERS frame header and payload both
+  // for headers and trailers.
+  EXPECT_CALL(*session_, WritevData(stream_, stream_->id(), _, _, _)).Times(4);
+  // PRIORITY_UPDATE frame on the control stream.
+  auto send_control_stream =
+      QuicSpdySessionPeer::GetSendControlStream(session_.get());
+  EXPECT_CALL(*session_, WritevData(send_control_stream,
+                                    send_control_stream->id(), _, _, _));
+
+  // Write the initial headers, without a FIN.
+  EXPECT_CALL(*stream_, WriteHeadersMock(false));
+  stream_->WriteHeaders(SpdyHeaderBlock(), /*fin=*/false, nullptr);
+
+  // Writing trailers implicitly sends a FIN.
+  SpdyHeaderBlock trailers;
+  trailers["trailer key"] = "trailer value";
+  EXPECT_CALL(*stream_, WriteHeadersMock(true));
+  stream_->WriteTrailers(std::move(trailers), nullptr);
+  EXPECT_TRUE(stream_->fin_sent());
+}
+
 // Test that when writing trailers, the trailers that are actually sent to the
 // peer contain the final offset field indicating last byte of data.
 TEST_P(QuicSpdyStreamTest, WritingTrailersFinalOffset) {
@@ -1287,8 +1317,14 @@
 
   if (UsesHttp3()) {
     // In this case, TestStream::WriteHeadersImpl() does not prevent writes.
+    // HEADERS frame header and payload on the request stream.
     EXPECT_CALL(*session_, WritevData(stream_, stream_->id(), _, _, _))
-        .Times(AtLeast(1));
+        .Times(2);
+    // PRIORITY_UPDATE frame on the control stream.
+    auto send_control_stream =
+        QuicSpdySessionPeer::GetSendControlStream(session_.get());
+    EXPECT_CALL(*session_, WritevData(send_control_stream,
+                                      send_control_stream->id(), _, _, _));
   }
 
   // Write the initial headers.
@@ -1333,6 +1369,13 @@
   // also written on the stream in case of IETF QUIC.
   EXPECT_CALL(*session_, WritevData(stream_, stream_->id(), _, _, _))
       .Times(AtLeast(1));
+  if (UsesHttp3()) {
+    // PRIORITY_UPDATE frame.
+    auto send_control_stream =
+        QuicSpdySessionPeer::GetSendControlStream(session_.get());
+    EXPECT_CALL(*session_, WritevData(send_control_stream,
+                                      send_control_stream->id(), _, _, _));
+  }
 
   // Write the initial headers.
   EXPECT_CALL(*stream_, WriteHeadersMock(false));
diff --git a/quic/tools/quic_simple_server_session_test.cc b/quic/tools/quic_simple_server_session_test.cc
index 4e3a50c..f416757 100644
--- a/quic/tools/quic_simple_server_session_test.cc
+++ b/quic/tools/quic_simple_server_session_test.cc
@@ -38,6 +38,7 @@
 #include "net/third_party/quiche/src/common/platform/api/quiche_text_utils.h"
 
 using testing::_;
+using testing::AnyNumber;
 using testing::AtLeast;
 using testing::InSequence;
 using testing::Invoke;
@@ -623,6 +624,14 @@
           /*is_static=*/true,
           spdy::SpdyStreamPrecedence(QuicStream::kDefaultPriority));
     }
+    if (VersionUsesHttp3(transport_version())) {
+      // Ignore writes on the control stream.
+      auto send_control_stream =
+          QuicSpdySessionPeer::GetSendControlStream(session_.get());
+      EXPECT_CALL(*connection_,
+                  SendStreamData(send_control_stream->id(), _, _, NO_FIN))
+          .Times(AnyNumber());
+    }
   }
 
   // Given |num_resources|, create this number of fake push resources and push