Implement PRIORITY_UPDATE frame in HttpEncoder and HttpDecoder.

gfe-relnote: n/a, change to QUIC v99-only code.  Protected by existing disabled gfe2_reloadable_flag_quic_enable_version_99.
PiperOrigin-RevId: 289120155
Change-Id: I85525c7fef4cccd7ecef9e1e24a704c656895f75
diff --git a/quic/core/http/http_decoder.cc b/quic/core/http/http_decoder.cc
index e3ca73e..f8a73f4 100644
--- a/quic/core/http/http_decoder.cc
+++ b/quic/core/http/http_decoder.cc
@@ -166,6 +166,9 @@
       break;
     case static_cast<uint64_t>(HttpFrameType::DUPLICATE_PUSH):
       break;
+    case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE):
+      continue_processing = visitor_->OnPriorityUpdateFrameStart(header_length);
+      break;
     default:
       continue_processing =
           visitor_->OnUnknownFrameStart(current_frame_type_, header_length);
@@ -289,6 +292,12 @@
       BufferFramePayload(reader);
       break;
     }
+    case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE): {
+      // TODO(bnc): Avoid buffering if the entire frame is present, and
+      // instead parse directly out of |reader|.
+      BufferFramePayload(reader);
+      break;
+    }
     default: {
       QuicByteCount bytes_to_read = std::min<QuicByteCount>(
           remaining_frame_length_, reader->BytesRemaining());
@@ -406,6 +415,17 @@
       continue_processing = visitor_->OnDuplicatePushFrame(frame);
       break;
     }
+    case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE): {
+      // TODO(bnc): Avoid buffering if the entire frame is present, and
+      // instead parse directly out of |reader|.
+      PriorityUpdateFrame frame;
+      QuicDataReader reader(buffer_.data(), current_frame_length_);
+      if (!ParsePriorityUpdateFrame(&reader, &frame)) {
+        return false;
+      }
+      continue_processing = visitor_->OnPriorityUpdateFrame(frame);
+      break;
+    }
     default: {
       continue_processing = visitor_->OnUnknownFrameEnd();
       break;
@@ -515,6 +535,41 @@
   return true;
 }
 
+bool HttpDecoder::ParsePriorityUpdateFrame(QuicDataReader* reader,
+                                           PriorityUpdateFrame* frame) {
+  uint8_t prioritized_element_type;
+  if (!reader->ReadUInt8(&prioritized_element_type)) {
+    // TODO(b/124216424): Use HTTP_MALFORMED_FRAME.
+    RaiseError(QUIC_INVALID_FRAME_DATA,
+               "Unable to read prioritized element type.");
+    return false;
+  }
+
+  if (prioritized_element_type != REQUEST_STREAM &&
+      prioritized_element_type != PUSH_STREAM) {
+    // TODO(b/124216424): Use HTTP_MALFORMED_FRAME.
+    RaiseError(QUIC_INVALID_FRAME_DATA, "Invalid prioritized element type.");
+    return false;
+  }
+
+  frame->prioritized_element_type =
+      static_cast<PrioritizedElementType>(prioritized_element_type);
+
+  if (!reader->ReadVarInt62(&frame->prioritized_element_id)) {
+    // TODO(b/124216424): Use HTTP_MALFORMED_FRAME.
+    RaiseError(QUIC_INVALID_FRAME_DATA,
+               "Unable to read prioritized element id.");
+    return false;
+  }
+
+  quiche::QuicheStringPiece priority_field_value =
+      reader->ReadRemainingPayload();
+  frame->priority_field_value =
+      std::string(priority_field_value.data(), priority_field_value.size());
+
+  return true;
+}
+
 QuicByteCount HttpDecoder::MaxFrameLength(uint64_t frame_type) {
   switch (frame_type) {
     case static_cast<uint64_t>(HttpFrameType::CANCEL_PUSH):
@@ -528,6 +583,9 @@
       return sizeof(PushId);
     case static_cast<uint64_t>(HttpFrameType::DUPLICATE_PUSH):
       return sizeof(PushId);
+    case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE):
+      // This limit is arbitrary.
+      return 1024 * 1024;
     default:
       // Other frames require no data buffering, so it's safe to have no limit.
       return std::numeric_limits<QuicByteCount>::max();
diff --git a/quic/core/http/http_decoder.h b/quic/core/http/http_decoder.h
index a77ce6c..426a86e 100644
--- a/quic/core/http/http_decoder.h
+++ b/quic/core/http/http_decoder.h
@@ -92,6 +92,13 @@
     // Called when a PUSH_PROMISE frame has been completely processed.
     virtual bool OnPushPromiseFrameEnd() = 0;
 
+    // Called when a PRIORITY_UPDATE frame has been received.
+    // |header_length| contains PRIORITY_UPDATE frame length and payload length.
+    virtual bool OnPriorityUpdateFrameStart(QuicByteCount header_length) = 0;
+
+    // Called when a PRIORITY_UPDATE frame has been successfully parsed.
+    virtual bool OnPriorityUpdateFrame(const PriorityUpdateFrame& frame) = 0;
+
     // Called when a frame of unknown type |frame_type| has been received.
     // Frame type might be reserved, Visitor must make sure to ignore.
     // |header_length| contains frame length and payload length.
@@ -177,6 +184,10 @@
   // Parses the payload of a SETTINGS frame from |reader| into |frame|.
   bool ParseSettingsFrame(QuicDataReader* reader, SettingsFrame* frame);
 
+  // Parses the payload of a PRIORITY_UPDATE frame from |reader| into |frame|.
+  bool ParsePriorityUpdateFrame(QuicDataReader* reader,
+                                PriorityUpdateFrame* frame);
+
   // Returns the max frame size of a given |frame_type|.
   QuicByteCount MaxFrameLength(uint64_t frame_type);
 
diff --git a/quic/core/http/http_decoder_test.cc b/quic/core/http/http_decoder_test.cc
index 942edfd..3a67395 100644
--- a/quic/core/http/http_decoder_test.cc
+++ b/quic/core/http/http_decoder_test.cc
@@ -63,6 +63,9 @@
                bool(quiche::QuicheStringPiece payload));
   MOCK_METHOD0(OnPushPromiseFrameEnd, bool());
 
+  MOCK_METHOD1(OnPriorityUpdateFrameStart, bool(QuicByteCount header_length));
+  MOCK_METHOD1(OnPriorityUpdateFrame, bool(const PriorityUpdateFrame& frame));
+
   MOCK_METHOD2(OnUnknownFrameStart, bool(uint64_t, QuicByteCount));
   MOCK_METHOD1(OnUnknownFramePayload, bool(quiche::QuicheStringPiece));
   MOCK_METHOD0(OnUnknownFrameEnd, bool());
@@ -88,6 +91,9 @@
         .WillByDefault(Return(true));
     ON_CALL(visitor_, OnPushPromiseFramePayload(_)).WillByDefault(Return(true));
     ON_CALL(visitor_, OnPushPromiseFrameEnd()).WillByDefault(Return(true));
+    ON_CALL(visitor_, OnPriorityUpdateFrameStart(_))
+        .WillByDefault(Return(true));
+    ON_CALL(visitor_, OnPriorityUpdateFrame(_)).WillByDefault(Return(true));
     ON_CALL(visitor_, OnUnknownFrameStart(_, _)).WillByDefault(Return(true));
     ON_CALL(visitor_, OnUnknownFramePayload(_)).WillByDefault(Return(true));
     ON_CALL(visitor_, OnUnknownFrameEnd()).WillByDefault(Return(true));
@@ -144,7 +150,7 @@
   const QuicByteCount payload_lengths[] = {0, 14, 100};
   const uint64_t frame_types[] = {
       0x21, 0x40, 0x5f, 0x7e, 0x9d,  // some reserved frame types
-      0x06, 0x0f, 0x14               // some unknown, not reserved frame types
+      0x06, 0x6f, 0x14               // some unknown, not reserved frame types
   };
 
   for (auto payload_length : payload_lengths) {
@@ -949,6 +955,124 @@
   EXPECT_EQ("", decoder_.error_detail());
 }
 
+TEST_F(HttpDecoderTest, PriorityUpdateFrame) {
+  InSequence s;
+  std::string input1 = quiche::QuicheTextUtils::HexDecode(
+      "0f"    // type (PRIORITY_UPDATE)
+      "02"    // length
+      "00"    // prioritized element type: REQUEST_STREAM
+      "03");  // prioritized element id
+
+  PriorityUpdateFrame priority_update1;
+  priority_update1.prioritized_element_type = REQUEST_STREAM;
+  priority_update1.prioritized_element_id = 0x03;
+
+  // Visitor pauses processing.
+  EXPECT_CALL(visitor_, OnPriorityUpdateFrameStart(2)).WillOnce(Return(false));
+  quiche::QuicheStringPiece remaining_input(input1);
+  QuicByteCount processed_bytes =
+      ProcessInputWithGarbageAppended(remaining_input);
+  EXPECT_EQ(2u, processed_bytes);
+  remaining_input = remaining_input.substr(processed_bytes);
+
+  EXPECT_CALL(visitor_, OnPriorityUpdateFrame(priority_update1))
+      .WillOnce(Return(false));
+  processed_bytes = ProcessInputWithGarbageAppended(remaining_input);
+  EXPECT_EQ(remaining_input.size(), processed_bytes);
+  EXPECT_THAT(decoder_.error(), IsQuicNoError());
+  EXPECT_EQ("", decoder_.error_detail());
+
+  // Process the full frame.
+  EXPECT_CALL(visitor_, OnPriorityUpdateFrameStart(2));
+  EXPECT_CALL(visitor_, OnPriorityUpdateFrame(priority_update1));
+  EXPECT_EQ(input1.size(), ProcessInput(input1));
+  EXPECT_THAT(decoder_.error(), IsQuicNoError());
+  EXPECT_EQ("", decoder_.error_detail());
+
+  // Process the frame incrementally.
+  EXPECT_CALL(visitor_, OnPriorityUpdateFrameStart(2));
+  EXPECT_CALL(visitor_, OnPriorityUpdateFrame(priority_update1));
+  ProcessInputCharByChar(input1);
+  EXPECT_THAT(decoder_.error(), IsQuicNoError());
+  EXPECT_EQ("", decoder_.error_detail());
+
+  std::string input2 = quiche::QuicheTextUtils::HexDecode(
+      "0f"        // type (PRIORIRTY)
+      "05"        // length
+      "80"        // prioritized element type: PUSH_STREAM
+      "05"        // prioritized element id
+      "666f6f");  // priority field value: "foo"
+
+  PriorityUpdateFrame priority_update2;
+  priority_update2.prioritized_element_type = PUSH_STREAM;
+  priority_update2.prioritized_element_id = 0x05;
+  priority_update2.priority_field_value = "foo";
+
+  // Visitor pauses processing.
+  EXPECT_CALL(visitor_, OnPriorityUpdateFrameStart(2)).WillOnce(Return(false));
+  remaining_input = input2;
+  processed_bytes = ProcessInputWithGarbageAppended(remaining_input);
+  EXPECT_EQ(2u, processed_bytes);
+  remaining_input = remaining_input.substr(processed_bytes);
+
+  EXPECT_CALL(visitor_, OnPriorityUpdateFrame(priority_update2))
+      .WillOnce(Return(false));
+  processed_bytes = ProcessInputWithGarbageAppended(remaining_input);
+  EXPECT_EQ(remaining_input.size(), processed_bytes);
+  EXPECT_THAT(decoder_.error(), IsQuicNoError());
+  EXPECT_EQ("", decoder_.error_detail());
+
+  // Process the full frame.
+  EXPECT_CALL(visitor_, OnPriorityUpdateFrameStart(2));
+  EXPECT_CALL(visitor_, OnPriorityUpdateFrame(priority_update2));
+  EXPECT_EQ(input2.size(), ProcessInput(input2));
+  EXPECT_THAT(decoder_.error(), IsQuicNoError());
+  EXPECT_EQ("", decoder_.error_detail());
+
+  // Process the frame incrementally.
+  EXPECT_CALL(visitor_, OnPriorityUpdateFrameStart(2));
+  EXPECT_CALL(visitor_, OnPriorityUpdateFrame(priority_update2));
+  ProcessInputCharByChar(input2);
+  EXPECT_THAT(decoder_.error(), IsQuicNoError());
+  EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, CorruptPriorityUpdateFrame) {
+  std::string payload1 = quiche::QuicheTextUtils::HexDecode(
+      "80"      // prioritized element type: PUSH_STREAM
+      "4005");  // prioritized element id
+  std::string payload2 = quiche::QuicheTextUtils::HexDecode(
+      "42");  // invalid prioritized element type
+  struct {
+    const char* const payload;
+    size_t payload_length;
+    const char* const error_message;
+  } kTestData[] = {
+      {payload1.data(), 0, "Unable to read prioritized element type."},
+      {payload1.data(), 1, "Unable to read prioritized element id."},
+      {payload1.data(), 2, "Unable to read prioritized element id."},
+      {payload2.data(), 1, "Invalid prioritized element type."},
+  };
+
+  for (const auto& test_data : kTestData) {
+    std::string input;
+    input.push_back(15u);  // type PRIORITY_UPDATE
+    input.push_back(test_data.payload_length);
+    size_t header_length = input.size();
+    input.append(test_data.payload, test_data.payload_length);
+
+    HttpDecoder decoder(&visitor_);
+    EXPECT_CALL(visitor_, OnPriorityUpdateFrameStart(header_length));
+    EXPECT_CALL(visitor_, OnError(&decoder));
+
+    QuicByteCount processed_bytes =
+        decoder.ProcessInput(input.data(), input.size());
+    EXPECT_EQ(input.size(), processed_bytes);
+    EXPECT_THAT(decoder.error(), IsError(QUIC_INVALID_FRAME_DATA));
+    EXPECT_EQ(test_data.error_message, decoder.error_detail());
+  }
+}
+
 }  // namespace test
 
 }  // namespace quic
diff --git a/quic/core/http/http_encoder.cc b/quic/core/http/http_encoder.cc
index 2f35897..00bb980 100644
--- a/quic/core/http/http_encoder.cc
+++ b/quic/core/http/http_encoder.cc
@@ -213,4 +213,32 @@
   return 0;
 }
 
+// static
+QuicByteCount HttpEncoder::SerializePriorityUpdateFrame(
+    const PriorityUpdateFrame& priority_update,
+    std::unique_ptr<char[]>* output) {
+  QuicByteCount payload_length =
+      kPriorityFirstByteLength +
+      QuicDataWriter::GetVarInt62Len(priority_update.prioritized_element_id) +
+      priority_update.priority_field_value.size();
+  QuicByteCount total_length =
+      GetTotalLength(payload_length, HttpFrameType::PRIORITY_UPDATE);
+
+  output->reset(new char[total_length]);
+  QuicDataWriter writer(total_length, output->get());
+
+  if (WriteFrameHeader(payload_length, HttpFrameType::PRIORITY_UPDATE,
+                       &writer) &&
+      writer.WriteUInt8(priority_update.prioritized_element_type) &&
+      writer.WriteVarInt62(priority_update.prioritized_element_id) &&
+      writer.WriteBytes(priority_update.priority_field_value.data(),
+                        priority_update.priority_field_value.size())) {
+    return total_length;
+  }
+
+  QUIC_DLOG(ERROR) << "Http encoder failed when attempting to serialize "
+                      "PRIORITY_UPDATE frame.";
+  return 0;
+}
+
 }  // namespace quic
diff --git a/quic/core/http/http_encoder.h b/quic/core/http/http_encoder.h
index b856187..ce58408 100644
--- a/quic/core/http/http_encoder.h
+++ b/quic/core/http/http_encoder.h
@@ -65,6 +65,12 @@
   static QuicByteCount SerializeDuplicatePushFrame(
       const DuplicatePushFrame& duplicate_push,
       std::unique_ptr<char[]>* output);
+
+  // Serializes a PRIORITY_UPDATE frame into a new buffer stored in |output|.
+  // Returns the length of the buffer on success, or 0 otherwise.
+  static QuicByteCount SerializePriorityUpdateFrame(
+      const PriorityUpdateFrame& priority_update,
+      std::unique_ptr<char[]>* output);
 };
 
 }  // namespace quic
diff --git a/quic/core/http/http_encoder_test.cc b/quic/core/http/http_encoder_test.cc
index 5ad79d8..316d6be 100644
--- a/quic/core/http/http_encoder_test.cc
+++ b/quic/core/http/http_encoder_test.cc
@@ -149,5 +149,38 @@
       "DUPLICATE_PUSH", buffer.get(), length, output, QUICHE_ARRAYSIZE(output));
 }
 
+TEST(HttpEncoderTest, SerializePriorityUpdateFrame) {
+  PriorityUpdateFrame priority_update1;
+  priority_update1.prioritized_element_type = REQUEST_STREAM;
+  priority_update1.prioritized_element_id = 0x03;
+  char output1[] = {0x0f,   // type (PRIORITY_UPDATE)
+                    0x02,   // length
+                    0x00,   // prioritized element type: REQUEST_STREAM
+                    0x03};  // prioritized element id
+
+  std::unique_ptr<char[]> buffer;
+  uint64_t length =
+      HttpEncoder::SerializePriorityUpdateFrame(priority_update1, &buffer);
+  EXPECT_EQ(QUICHE_ARRAYSIZE(output1), length);
+  quiche::test::CompareCharArraysWithHexError("PRIORITY_UPDATE", buffer.get(),
+                                              length, output1,
+                                              QUICHE_ARRAYSIZE(output1));
+
+  PriorityUpdateFrame priority_update2;
+  priority_update2.prioritized_element_type = PUSH_STREAM;
+  priority_update2.prioritized_element_id = 0x05;
+  priority_update2.priority_field_value = "foo";
+  char output2[] = {0x0f,             // type (PRIORIRTY)
+                    0x05,             // length
+                    0x80,             // prioritized element type: PUSH_STREAM
+                    0x05,             // prioritized element id
+                    'f',  'o', 'o'};  // priority field value
+  length = HttpEncoder::SerializePriorityUpdateFrame(priority_update2, &buffer);
+  EXPECT_EQ(QUICHE_ARRAYSIZE(output2), length);
+  quiche::test::CompareCharArraysWithHexError("PRIORITY_UPDATE", buffer.get(),
+                                              length, output2,
+                                              QUICHE_ARRAYSIZE(output2));
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/http/http_frames.h b/quic/core/http/http_frames.h
index 6ad6da7..e1e184f 100644
--- a/quic/core/http/http_frames.h
+++ b/quic/core/http/http_frames.h
@@ -26,7 +26,8 @@
   PUSH_PROMISE = 0x5,
   GOAWAY = 0x7,
   MAX_PUSH_ID = 0xD,
-  DUPLICATE_PUSH = 0xE
+  DUPLICATE_PUSH = 0xE,
+  PRIORITY_UPDATE = 0XF,
 };
 
 // 7.2.1.  DATA
@@ -142,6 +143,46 @@
   }
 };
 
+// https://httpwg.org/http-extensions/draft-ietf-httpbis-priority.html
+//
+//   The PRIORITY_UPDATE (type=0x0f) frame specifies the sender-advised priority
+//   of a stream
+
+// Length of a priority frame's first byte.
+const QuicByteCount kPriorityFirstByteLength = 1;
+
+enum PrioritizedElementType : uint8_t {
+  REQUEST_STREAM = 0x00,
+  PUSH_STREAM = 0x80,
+};
+
+struct QUIC_EXPORT_PRIVATE PriorityUpdateFrame {
+  PrioritizedElementType prioritized_element_type = REQUEST_STREAM;
+  uint64_t prioritized_element_id = 0;
+  std::string priority_field_value;
+
+  bool operator==(const PriorityUpdateFrame& rhs) const {
+    return std::tie(prioritized_element_type, prioritized_element_id,
+                    priority_field_value) ==
+           std::tie(rhs.prioritized_element_type, rhs.prioritized_element_id,
+                    rhs.priority_field_value);
+  }
+  std::string ToString() const {
+    return quiche::QuicheStrCat(
+        "Priority Frame : {prioritized_element_type: ",
+        static_cast<int>(prioritized_element_type),
+        ", prioritized_element_id: ", prioritized_element_id,
+        ", priority_field_value: ", priority_field_value, "}");
+  }
+
+  friend QUIC_EXPORT_PRIVATE std::ostream& operator<<(
+      std::ostream& os,
+      const PriorityUpdateFrame& s) {
+    os << s.ToString();
+    return os;
+  }
+};
+
 }  // namespace quic
 
 #endif  // QUICHE_QUIC_CORE_HTTP_HTTP_FRAMES_H_
diff --git a/quic/core/http/quic_receive_control_stream.cc b/quic/core/http/quic_receive_control_stream.cc
index 2269faf..283b54e 100644
--- a/quic/core/http/quic_receive_control_stream.cc
+++ b/quic/core/http/quic_receive_control_stream.cc
@@ -122,6 +122,16 @@
     return false;
   }
 
+  bool OnPriorityUpdateFrameStart(QuicByteCount /*header_length*/) override {
+    // TODO(b/147306124): Implement.
+    return true;
+  }
+
+  bool OnPriorityUpdateFrame(const PriorityUpdateFrame& /*frame*/) override {
+    // TODO(b/147306124): Implement.
+    return true;
+  }
+
   bool OnUnknownFrameStart(uint64_t /* frame_type */,
                            QuicByteCount /* header_length */) override {
     // Ignore unknown frame types.
diff --git a/quic/core/http/quic_receive_control_stream.h b/quic/core/http/quic_receive_control_stream.h
index 58a0be7..52f9663 100644
--- a/quic/core/http/quic_receive_control_stream.h
+++ b/quic/core/http/quic_receive_control_stream.h
@@ -38,6 +38,8 @@
   // Called from HttpDecoderVisitor.
   bool OnSettingsFrameStart(QuicByteCount header_length);
   bool OnSettingsFrame(const SettingsFrame& settings);
+  bool OnPriorityUpdateFrameStart(QuicByteCount header_length);
+  bool OnPriorityUpdateFrame(const PriorityUpdateFrame& priority);
 
   // False until a SETTINGS frame is received.
   bool settings_frame_received_;
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
index 4d36df0..819caef 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -147,6 +147,16 @@
     return stream_->OnPushPromiseFrameEnd();
   }
 
+  bool OnPriorityUpdateFrameStart(QuicByteCount /*header_length*/) override {
+    CloseConnectionOnWrongFrame("Priority update");
+    return false;
+  }
+
+  bool OnPriorityUpdateFrame(const PriorityUpdateFrame& /*frame*/) override {
+    CloseConnectionOnWrongFrame("Priority update");
+    return false;
+  }
+
   bool OnUnknownFrameStart(uint64_t frame_type,
                            QuicByteCount header_length) override {
     return stream_->OnUnknownFrameStart(frame_type, header_length);