Read and interpret 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: 290869051
Change-Id: Ie5c016bd4254bffad7910d248e483976a249a27d
diff --git a/quic/core/http/quic_receive_control_stream.cc b/quic/core/http/quic_receive_control_stream.cc
index f07e900..c66e696 100644
--- a/quic/core/http/quic_receive_control_stream.cc
+++ b/quic/core/http/quic_receive_control_stream.cc
@@ -11,6 +11,7 @@
 #include "net/third_party/quiche/src/quic/core/http/quic_spdy_session.h"
 #include "net/third_party/quiche/src/quic/core/quic_types.h"
 #include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h"
+#include "net/third_party/quiche/src/common/platform/api/quiche_text_utils.h"
 
 namespace quic {
 
@@ -36,10 +37,8 @@
   }
 
   bool OnMaxPushIdFrame(const MaxPushIdFrame& frame) override {
-    if (stream_->session()->perspective() == Perspective::IS_SERVER) {
-      QuicSpdySession* spdy_session =
-          static_cast<QuicSpdySession*>(stream_->session());
-      spdy_session->SetMaxAllowedPushId(frame.push_id);
+    if (stream_->spdy_session()->perspective() == Perspective::IS_SERVER) {
+      stream_->spdy_session()->SetMaxAllowedPushId(frame.push_id);
       return true;
     }
     CloseConnectionOnWrongFrame("Max Push Id");
@@ -47,13 +46,11 @@
   }
 
   bool OnGoAwayFrame(const GoAwayFrame& frame) override {
-    QuicSpdySession* spdy_session =
-        static_cast<QuicSpdySession*>(stream_->session());
-    if (spdy_session->perspective() == Perspective::IS_SERVER) {
+    if (stream_->spdy_session()->perspective() == Perspective::IS_SERVER) {
       CloseConnectionOnWrongFrame("Go Away");
       return false;
     }
-    spdy_session->OnHttp3GoAway(frame.stream_id);
+    stream_->spdy_session()->OnHttp3GoAway(frame.stream_id);
     return true;
   }
 
@@ -122,14 +119,12 @@
     return false;
   }
 
-  bool OnPriorityUpdateFrameStart(QuicByteCount /*header_length*/) override {
-    // TODO(b/147306124): Implement.
-    return true;
+  bool OnPriorityUpdateFrameStart(QuicByteCount header_length) override {
+    return stream_->OnPriorityUpdateFrameStart(header_length);
   }
 
-  bool OnPriorityUpdateFrame(const PriorityUpdateFrame& /*frame*/) override {
-    // TODO(b/147306124): Implement.
-    return true;
+  bool OnPriorityUpdateFrame(const PriorityUpdateFrame& frame) override {
+    return stream_->OnPriorityUpdateFrame(frame);
   }
 
   bool OnUnknownFrameStart(uint64_t /* frame_type */,
@@ -159,11 +154,14 @@
   QuicReceiveControlStream* stream_;
 };
 
-QuicReceiveControlStream::QuicReceiveControlStream(PendingStream* pending)
+QuicReceiveControlStream::QuicReceiveControlStream(
+    PendingStream* pending,
+    QuicSpdySession* spdy_session)
     : QuicStream(pending, READ_UNIDIRECTIONAL, /*is_static=*/true),
       settings_frame_received_(false),
       http_decoder_visitor_(std::make_unique<HttpDecoderVisitor>(this)),
-      decoder_(http_decoder_visitor_.get()) {
+      decoder_(http_decoder_visitor_.get()),
+      spdy_session_(spdy_session) {
   sequencer()->set_level_triggered(true);
 }
 
@@ -216,14 +214,65 @@
 bool QuicReceiveControlStream::OnSettingsFrame(const SettingsFrame& settings) {
   QUIC_DVLOG(1) << "Control Stream " << id()
                 << " received settings frame: " << settings;
-  QuicSpdySession* spdy_session = static_cast<QuicSpdySession*>(session());
-  if (spdy_session->debug_visitor() != nullptr) {
-    spdy_session->debug_visitor()->OnSettingsFrameReceived(settings);
+  if (spdy_session_->debug_visitor() != nullptr) {
+    spdy_session_->debug_visitor()->OnSettingsFrameReceived(settings);
   }
   for (const auto& setting : settings.values) {
-    spdy_session->OnSetting(setting.first, setting.second);
+    spdy_session_->OnSetting(setting.first, setting.second);
   }
   return true;
 }
 
+bool QuicReceiveControlStream::OnPriorityUpdateFrameStart(
+    QuicByteCount /* header_length */) {
+  if (!settings_frame_received_) {
+    session()->connection()->CloseConnection(
+        QUIC_INVALID_STREAM_ID,
+        "PRIORITY_UPDATE frame received before SETTINGS.",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return false;
+  }
+  return true;
+}
+
+bool QuicReceiveControlStream::OnPriorityUpdateFrame(
+    const PriorityUpdateFrame& priority) {
+  // TODO(b/147306124): Use a proper structured headers parser instead.
+  for (auto key_value :
+       quiche::QuicheTextUtils::Split(priority.priority_field_value, ',')) {
+    auto key_and_value = quiche::QuicheTextUtils::Split(key_value, '=');
+    if (key_and_value.size() != 2) {
+      continue;
+    }
+
+    quiche::QuicheStringPiece key = key_and_value[0];
+    quiche::QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(&key);
+    if (key != "u") {
+      continue;
+    }
+
+    quiche::QuicheStringPiece value = key_and_value[1];
+    int urgency;
+    if (!quiche::QuicheTextUtils::StringToInt(value, &urgency) || urgency < 0 ||
+        urgency > 7) {
+      session()->connection()->CloseConnection(
+          QUIC_INVALID_STREAM_ID,
+          "Invalid value for PRIORITY_UPDATE urgency parameter.",
+          ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+      return false;
+    }
+
+    if (priority.prioritized_element_type == REQUEST_STREAM) {
+      return spdy_session_->OnPriorityUpdateForRequestStream(
+          priority.prioritized_element_id, urgency);
+    } else {
+      return spdy_session_->OnPriorityUpdateForPushStream(
+          priority.prioritized_element_id, urgency);
+    }
+  }
+
+  // Ignore frame if no urgency parameter can be parsed.
+  return true;
+}
+
 }  // namespace quic
diff --git a/quic/core/http/quic_receive_control_stream.h b/quic/core/http/quic_receive_control_stream.h
index 52f9663..e77ccf9 100644
--- a/quic/core/http/quic_receive_control_stream.h
+++ b/quic/core/http/quic_receive_control_stream.h
@@ -18,7 +18,8 @@
 // The receive control stream is peer initiated and is read only.
 class QUIC_EXPORT_PRIVATE QuicReceiveControlStream : public QuicStream {
  public:
-  explicit QuicReceiveControlStream(PendingStream* pending);
+  explicit QuicReceiveControlStream(PendingStream* pending,
+                                    QuicSpdySession* spdy_session);
   QuicReceiveControlStream(const QuicReceiveControlStream&) = delete;
   QuicReceiveControlStream& operator=(const QuicReceiveControlStream&) = delete;
   ~QuicReceiveControlStream() override;
@@ -32,6 +33,8 @@
 
   void SetUnblocked() { sequencer()->SetUnblocked(); }
 
+  QuicSpdySession* spdy_session() { return spdy_session_; }
+
  private:
   class HttpDecoderVisitor;
 
@@ -47,6 +50,8 @@
   // HttpDecoder and its visitor.
   std::unique_ptr<HttpDecoderVisitor> http_decoder_visitor_;
   HttpDecoder decoder_;
+
+  QuicSpdySession* const spdy_session_;
 };
 
 }  // namespace quic
diff --git a/quic/core/http/quic_receive_control_stream_test.cc b/quic/core/http/quic_receive_control_stream_test.cc
index c669f85..489d983 100644
--- a/quic/core/http/quic_receive_control_stream_test.cc
+++ b/quic/core/http/quic_receive_control_stream_test.cc
@@ -110,6 +110,15 @@
     return std::string(buffer.get(), settings_frame_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);
+  }
+
   QuicStreamOffset NumBytesConsumed() {
     return QuicStreamPeer::sequencer(receive_control_stream_)
         ->NumBytesConsumed();
@@ -220,6 +229,24 @@
   receive_control_stream_->OnStreamFrame(frame);
 }
 
+TEST_P(QuicReceiveControlStreamTest,
+       ReceivePriorityUpdateFrameBeforeSettingsFrame) {
+  std::string serialized_frame = SerializePriorityUpdateFrame({});
+  QuicStreamFrame data(receive_control_stream_->id(), /* fin = */ false,
+                       /* offset = */ 1, serialized_frame);
+
+  EXPECT_CALL(
+      *connection_,
+      CloseConnection(QUIC_INVALID_STREAM_ID,
+                      "PRIORITY_UPDATE frame received before SETTINGS.", _))
+      .WillOnce(
+          Invoke(connection_, &MockQuicConnection::ReallyCloseConnection));
+  EXPECT_CALL(*connection_, SendConnectionClosePacket(_, _));
+  EXPECT_CALL(session_, OnConnectionClosed(_, _));
+
+  receive_control_stream_->OnStreamFrame(data);
+}
+
 TEST_P(QuicReceiveControlStreamTest, ReceiveGoAwayFrame) {
   GoAwayFrame goaway;
   goaway.stream_id = 0x00;
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc
index d177419..301e1d1 100644
--- a/quic/core/http/quic_spdy_session.cc
+++ b/quic/core/http/quic_spdy_session.cc
@@ -496,6 +496,29 @@
   stream->OnPriorityFrame(precedence);
 }
 
+bool QuicSpdySession::OnPriorityUpdateForRequestStream(QuicStreamId stream_id,
+                                                       int urgency) {
+  if (perspective() == Perspective::IS_CLIENT ||
+      !QuicUtils::IsBidirectionalStreamId(stream_id) ||
+      !QuicUtils::IsClientInitiatedStreamId(transport_version(), stream_id)) {
+    return true;
+  }
+
+  // TODO(b/147306124): Signal error on invalid stream_id.
+
+  MaybeSetStreamPriority(stream_id, spdy::SpdyStreamPrecedence(urgency));
+
+  // TODO(b/147306124): Buffer |urgency| for streams not open yet.
+
+  return true;
+}
+
+bool QuicSpdySession::OnPriorityUpdateForPushStream(QuicStreamId /*push_id*/,
+                                                    int /*urgency*/) {
+  // TODO(b/147306124): Implement PRIORITY_UPDATE frames for pushed streams.
+  return true;
+}
+
 size_t QuicSpdySession::ProcessHeaderData(const struct iovec& iov) {
   QUIC_BUG_IF(destruction_indicator_ != 123456789)
       << "QuicSpdyStream use after free. " << destruction_indicator_
@@ -941,7 +964,8 @@
         CloseConnectionOnDuplicateHttp3UnidirectionalStreams("Control");
         return false;
       }
-      auto receive_stream = std::make_unique<QuicReceiveControlStream>(pending);
+      auto receive_stream =
+          std::make_unique<QuicReceiveControlStream>(pending, this);
       receive_control_stream_ = receive_stream.get();
       ActivateStream(std::move(receive_stream));
       receive_control_stream_->SetUnblocked();
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h
index 9f7349f..53c7c09 100644
--- a/quic/core/http/quic_spdy_session.h
+++ b/quic/core/http/quic_spdy_session.h
@@ -127,6 +127,14 @@
   virtual void OnPriorityFrame(QuicStreamId stream_id,
                                const spdy::SpdyStreamPrecedence& precedence);
 
+  // Called when an HTTP/3 PRIORITY_UPDATE frame has been received for a request
+  // stream.  Returns false and closes connection if |stream_id| is invalid.
+  bool OnPriorityUpdateForRequestStream(QuicStreamId stream_id, int urgency);
+
+  // Called when an HTTP/3 PRIORITY_UPDATE frame has been received for a push
+  // stream.  Returns false and closes connection if |push_id| is invalid.
+  bool OnPriorityUpdateForPushStream(QuicStreamId push_id, int urgency);
+
   // Sends contents of |iov| to h2_deframer_, returns number of bytes processed.
   size_t ProcessHeaderData(const struct iovec& iov);
 
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index 319f3f4..5f3ea29 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -1644,6 +1644,18 @@
   }
 }
 
+bool QuicSession::MaybeSetStreamPriority(
+    QuicStreamId stream_id,
+    const spdy::SpdyStreamPrecedence& precedence) {
+  auto active_stream = stream_map_.find(stream_id);
+  if (active_stream != stream_map_.end()) {
+    active_stream->second->SetPriority(precedence);
+    return true;
+  }
+
+  return false;
+}
+
 bool QuicSession::IsClosedStream(QuicStreamId id) {
   DCHECK_NE(QuicUtils::GetInvalidStreamId(transport_version()), id);
   if (IsOpenStream(id)) {
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h
index 6aa2b0a..f473156 100644
--- a/quic/core/quic_session.h
+++ b/quic/core/quic_session.h
@@ -636,6 +636,10 @@
   // times is safe.
   void DeleteConnection();
 
+  // Call SetPriority() on stream id |id| and return true if stream is active.
+  bool MaybeSetStreamPriority(QuicStreamId stream_id,
+                              const spdy::SpdyStreamPrecedence& precedence);
+
  private:
   friend class test::QuicSessionPeer;