Buffer priority values in PRIORITY_UPDATE frames for not yet open streams.

gfe-relnote: n/a, change to QUIC v99-only code.  Protected by existing disabled gfe2_reloadable_flag_quic_enable_version_99.
PiperOrigin-RevId: 291618753
Change-Id: Ibf7d8545cc122f66bd9d1b7032b2aece751c3ed9
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc
index db93a86..c446160 100644
--- a/quic/core/http/quic_spdy_session.cc
+++ b/quic/core/http/quic_spdy_session.cc
@@ -518,9 +518,32 @@
     return false;
   }
 
-  MaybeSetStreamPriority(stream_id, spdy::SpdyStreamPrecedence(urgency));
+  if (MaybeSetStreamPriority(stream_id, spdy::SpdyStreamPrecedence(urgency))) {
+    return true;
+  }
 
-  // TODO(b/147306124): Buffer |urgency| for streams not open yet.
+  if (IsClosedStream(stream_id)) {
+    return true;
+  }
+
+  buffered_stream_priorities_[stream_id] = urgency;
+
+  if (buffered_stream_priorities_.size() >
+      10 * max_open_incoming_bidirectional_streams()) {
+    // This should never happen, because |buffered_stream_priorities_| should
+    // only contain entries for streams that are allowed to be open by the peer
+    // but have not been opened yet.
+    std::string error_message = quiche::QuicheStrCat(
+        "Too many stream priority values buffered: ",
+        buffered_stream_priorities_.size(),
+        ", which should not exceed the incoming stream limit of ",
+        max_open_incoming_bidirectional_streams());
+    QUIC_BUG << error_message;
+    connection()->CloseConnection(
+        QUIC_INTERNAL_ERROR, error_message,
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return false;
+  }
 
   return true;
 }
@@ -657,6 +680,16 @@
   return qpack_decoder_.get();
 }
 
+void QuicSpdySession::OnStreamCreated(QuicSpdyStream* stream) {
+  auto it = buffered_stream_priorities_.find(stream->id());
+  if (it == buffered_stream_priorities_.end()) {
+    return;
+  }
+
+  stream->SetPriority(spdy::SpdyStreamPrecedence(it->second));
+  buffered_stream_priorities_.erase(it);
+}
+
 QuicSpdyStream* QuicSpdySession::GetOrCreateSpdyDataStream(
     const QuicStreamId stream_id) {
   QuicStream* stream = GetOrCreateStream(stream_id);
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h
index 53c7c09..71a5c61 100644
--- a/quic/core/http/quic_spdy_session.h
+++ b/quic/core/http/quic_spdy_session.h
@@ -281,6 +281,8 @@
            (qpack_decoder_ && qpack_decoder_->dynamic_table_entry_referenced());
   }
 
+  void OnStreamCreated(QuicSpdyStream* stream);
+
  protected:
   // Override CreateIncomingStream(), CreateOutgoingBidirectionalStream() and
   // CreateOutgoingUnidirectionalStream() with QuicSpdyStream return type to
@@ -451,6 +453,10 @@
 
   // If the endpoint has sent the initial HTTP/3 MAX_PUSH_ID frame.
   bool http3_max_push_id_sent_;
+
+  // Priority values received in PRIORITY_UPDATE frames for streams that are not
+  // open yet.
+  QuicUnorderedMap<QuicStreamId, int> buffered_stream_priorities_;
 };
 
 }  // namespace quic
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index 1082633..11c5fda 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -420,6 +420,15 @@
     return std::string(buffer.get(), header_length);
   }
 
+  std::string SerializePriorityUpdateFrame(
+      const PriorityUpdateFrame& priority_update) {
+    std::unique_ptr<char[]> priority_buffer;
+    QuicByteCount priority_frame_length =
+        HttpEncoder::SerializePriorityUpdateFrame(priority_update,
+                                                  &priority_buffer);
+    return std::string(priority_buffer.get(), priority_frame_length);
+  }
+
   QuicStreamId StreamCountToId(QuicStreamCount stream_count,
                                Perspective perspective,
                                bool bidirectional) {
@@ -2134,6 +2143,68 @@
             stream->precedence());
 }
 
+TEST_P(QuicSpdySessionTestServer, OnPriorityUpdateFrame) {
+  if (!VersionUsesHttp3(transport_version())) {
+    return;
+  }
+
+  // Create control stream.
+  QuicStreamId receive_control_stream_id =
+      GetNthClientInitiatedUnidirectionalStreamId(transport_version(), 3);
+  char type[] = {kControlStream};
+  quiche::QuicheStringPiece stream_type(type, 1);
+  QuicStreamOffset offset = 0;
+  QuicStreamFrame data1(receive_control_stream_id, false, offset, stream_type);
+  offset += stream_type.length();
+  session_.OnStreamFrame(data1);
+  EXPECT_EQ(receive_control_stream_id,
+            QuicSpdySessionPeer::GetReceiveControlStream(&session_)->id());
+
+  // Send SETTINGS frame.
+  std::string serialized_settings = EncodeSettings({});
+  QuicStreamFrame data2(receive_control_stream_id, false, offset,
+                        serialized_settings);
+  offset += serialized_settings.length();
+  session_.OnStreamFrame(data2);
+
+  // PRIORITY_UPDATE frame for first request stream.
+  const QuicStreamId stream_id1 = GetNthClientInitiatedBidirectionalId(0);
+  struct PriorityUpdateFrame priority_update1;
+  priority_update1.prioritized_element_type = REQUEST_STREAM;
+  priority_update1.prioritized_element_id = stream_id1;
+  priority_update1.priority_field_value = "u=1";
+  std::string serialized_priority_update1 =
+      SerializePriorityUpdateFrame(priority_update1);
+  QuicStreamFrame data3(receive_control_stream_id,
+                        /* fin = */ false, offset, serialized_priority_update1);
+  offset += serialized_priority_update1.size();
+
+  // PRIORITY_UPDATE frame arrives after stream creation.
+  TestStream* stream1 = session_.CreateIncomingStream(stream_id1);
+  EXPECT_EQ(3u, stream1->precedence().spdy3_priority());
+  session_.OnStreamFrame(data3);
+  EXPECT_EQ(1u, stream1->precedence().spdy3_priority());
+
+  // PRIORITY_UPDATE frame for second request stream.
+  const QuicStreamId stream_id2 = GetNthClientInitiatedBidirectionalId(1);
+  struct PriorityUpdateFrame priority_update2;
+  priority_update2.prioritized_element_type = REQUEST_STREAM;
+  priority_update2.prioritized_element_id = stream_id2;
+  priority_update2.priority_field_value = "u=2";
+  std::string serialized_priority_update2 =
+      SerializePriorityUpdateFrame(priority_update2);
+  QuicStreamFrame stream_frame3(receive_control_stream_id,
+                                /* fin = */ false, offset,
+                                serialized_priority_update2);
+
+  // PRIORITY_UPDATE frame arrives before stream creation,
+  // priority value is buffered.
+  session_.OnStreamFrame(stream_frame3);
+  // Priority is applied upon stream construction.
+  TestStream* stream2 = session_.CreateIncomingStream(stream_id2);
+  EXPECT_EQ(2u, stream2->precedence().spdy3_priority());
+}
+
 TEST_P(QuicSpdySessionTestServer, SimplePendingStreamType) {
   if (!VersionUsesHttp3(transport_version())) {
     return;
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
index 5d5c176..29e0660 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -217,6 +217,8 @@
   if (VersionUsesHttp3(transport_version())) {
     sequencer()->set_level_triggered(true);
   }
+
+  spdy_session_->OnStreamCreated(this);
 }
 
 QuicSpdyStream::QuicSpdyStream(PendingStream* pending,
@@ -251,6 +253,8 @@
   if (VersionUsesHttp3(transport_version())) {
     sequencer()->set_level_triggered(true);
   }
+
+  spdy_session_->OnStreamCreated(this);
 }
 
 QuicSpdyStream::~QuicSpdyStream() {}