Implement PRIORITY_UPDATE frame in HTTP/2 decoder.

Protected by FLAGS_gfe2_restart_flag_http2_parse_priority_update_frame.

PiperOrigin-RevId: 350771697
Change-Id: Ia630bd2d2a2211c8f6238676f6024dcf7ada924d
diff --git a/http2/decoder/decode_http2_structures.cc b/http2/decoder/decode_http2_structures.cc
index 9d57058..2f0b06e 100644
--- a/http2/decoder/decode_http2_structures.cc
+++ b/http2/decoder/decode_http2_structures.cc
@@ -100,6 +100,15 @@
   out->window_size_increment = b->DecodeUInt31();
 }
 
+// Http2PriorityUpdateFields decoding:
+
+void DoDecode(Http2PriorityUpdateFields* out, DecodeBuffer* b) {
+  DCHECK_NE(nullptr, out);
+  DCHECK_NE(nullptr, b);
+  DCHECK_LE(Http2PriorityUpdateFields::EncodedSize(), b->Remaining());
+  out->prioritized_stream_id = b->DecodeUInt31();
+}
+
 // Http2AltSvcFields decoding:
 
 void DoDecode(Http2AltSvcFields* out, DecodeBuffer* b) {
diff --git a/http2/decoder/decode_http2_structures.h b/http2/decoder/decode_http2_structures.h
index 4cb93a3..9b26d33 100644
--- a/http2/decoder/decode_http2_structures.h
+++ b/http2/decoder/decode_http2_structures.h
@@ -28,6 +28,8 @@
 QUICHE_EXPORT_PRIVATE void DoDecode(Http2WindowUpdateFields* out,
                                     DecodeBuffer* b);
 QUICHE_EXPORT_PRIVATE void DoDecode(Http2AltSvcFields* out, DecodeBuffer* b);
+QUICHE_EXPORT_PRIVATE void DoDecode(Http2PriorityUpdateFields* out,
+                                    DecodeBuffer* b);
 
 }  // namespace http2
 
diff --git a/http2/decoder/http2_frame_decoder.cc b/http2/decoder/http2_frame_decoder.cc
index dc04c20..e395a27 100644
--- a/http2/decoder/http2_frame_decoder.cc
+++ b/http2/decoder/http2_frame_decoder.cc
@@ -8,6 +8,8 @@
 #include "http2/hpack/varint/hpack_varint_decoder.h"
 #include "http2/http2_constants.h"
 #include "http2/platform/api/http2_bug_tracker.h"
+#include "http2/platform/api/http2_flag_utils.h"
+#include "http2/platform/api/http2_flags.h"
 #include "http2/platform/api/http2_macros.h"
 
 namespace http2 {
@@ -155,6 +157,15 @@
       status = StartDecodingAltSvcPayload(&subset);
       break;
 
+    case Http2FrameType::PRIORITY_UPDATE:
+      if (GetHttp2RestartFlag(http2_parse_priority_update_frame)) {
+        HTTP2_RESTART_FLAG_COUNT_N(http2_parse_priority_update_frame, 1, 2);
+        status = StartDecodingPriorityUpdatePayload(&subset);
+      } else {
+        status = StartDecodingUnknownPayload(&subset);
+      }
+      break;
+
     default:
       status = StartDecodingUnknownPayload(&subset);
       break;
@@ -225,6 +236,15 @@
       status = ResumeDecodingAltSvcPayload(&subset);
       break;
 
+    case Http2FrameType::PRIORITY_UPDATE:
+      if (GetHttp2RestartFlag(http2_parse_priority_update_frame)) {
+        HTTP2_RESTART_FLAG_COUNT_N(http2_parse_priority_update_frame, 2, 2);
+        status = ResumeDecodingPriorityUpdatePayload(&subset);
+      } else {
+        status = ResumeDecodingUnknownPayload(&subset);
+      }
+      break;
+
     default:
       status = ResumeDecodingUnknownPayload(&subset);
       break;
@@ -339,6 +359,21 @@
                                                          db);
 }
 
+DecodeStatus Http2FrameDecoder::StartDecodingPriorityUpdatePayload(
+    DecodeBuffer* db) {
+  ClearFlags();
+  return priority_payload_update_decoder_.StartDecodingPayload(
+      &frame_decoder_state_, db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingPriorityUpdatePayload(
+    DecodeBuffer* db) {
+  // The frame is not paddable.
+  DCHECK_EQ(frame_decoder_state_.remaining_total_payload(),
+            frame_decoder_state_.remaining_payload());
+  return priority_payload_update_decoder_.ResumeDecodingPayload(
+      &frame_decoder_state_, db);
+}
+
 DecodeStatus Http2FrameDecoder::StartDecodingPushPromisePayload(
     DecodeBuffer* db) {
   RetainFlags(Http2FrameFlag::END_HEADERS | Http2FrameFlag::PADDED);
diff --git a/http2/decoder/http2_frame_decoder.h b/http2/decoder/http2_frame_decoder.h
index cb8f15b..53e4bbe 100644
--- a/http2/decoder/http2_frame_decoder.h
+++ b/http2/decoder/http2_frame_decoder.h
@@ -32,6 +32,7 @@
 #include "http2/decoder/payload_decoders/headers_payload_decoder.h"
 #include "http2/decoder/payload_decoders/ping_payload_decoder.h"
 #include "http2/decoder/payload_decoders/priority_payload_decoder.h"
+#include "http2/decoder/payload_decoders/priority_update_payload_decoder.h"
 #include "http2/decoder/payload_decoders/push_promise_payload_decoder.h"
 #include "http2/decoder/payload_decoders/rst_stream_payload_decoder.h"
 #include "http2/decoder/payload_decoders/settings_payload_decoder.h"
@@ -148,6 +149,7 @@
   DecodeStatus StartDecodingHeadersPayload(DecodeBuffer* db);
   DecodeStatus StartDecodingPingPayload(DecodeBuffer* db);
   DecodeStatus StartDecodingPriorityPayload(DecodeBuffer* db);
+  DecodeStatus StartDecodingPriorityUpdatePayload(DecodeBuffer* db);
   DecodeStatus StartDecodingPushPromisePayload(DecodeBuffer* db);
   DecodeStatus StartDecodingRstStreamPayload(DecodeBuffer* db);
   DecodeStatus StartDecodingSettingsPayload(DecodeBuffer* db);
@@ -169,6 +171,7 @@
   DecodeStatus ResumeDecodingHeadersPayload(DecodeBuffer* db);
   DecodeStatus ResumeDecodingPingPayload(DecodeBuffer* db);
   DecodeStatus ResumeDecodingPriorityPayload(DecodeBuffer* db);
+  DecodeStatus ResumeDecodingPriorityUpdatePayload(DecodeBuffer* db);
   DecodeStatus ResumeDecodingPushPromisePayload(DecodeBuffer* db);
   DecodeStatus ResumeDecodingRstStreamPayload(DecodeBuffer* db);
   DecodeStatus ResumeDecodingSettingsPayload(DecodeBuffer* db);
@@ -186,6 +189,7 @@
     HeadersPayloadDecoder headers_payload_decoder_;
     PingPayloadDecoder ping_payload_decoder_;
     PriorityPayloadDecoder priority_payload_decoder_;
+    PriorityUpdatePayloadDecoder priority_payload_update_decoder_;
     PushPromisePayloadDecoder push_promise_payload_decoder_;
     RstStreamPayloadDecoder rst_stream_payload_decoder_;
     SettingsPayloadDecoder settings_payload_decoder_;
diff --git a/http2/decoder/http2_frame_decoder_listener.h b/http2/decoder/http2_frame_decoder_listener.h
index 5482251..9bb1688 100644
--- a/http2/decoder/http2_frame_decoder_listener.h
+++ b/http2/decoder/http2_frame_decoder_listener.h
@@ -247,6 +247,28 @@
   // via the above methods.
   virtual void OnAltSvcEnd() = 0;
 
+  // Called when an PRIORITY_UPDATE frame header and Prioritized Stream ID have
+  // been parsed.  Afterwards:
+  //   OnPriorityUpdatePayload will be called each time a portion of the
+  //     Priority Field Value field is available until all of it has been
+  //     provided;
+  //   OnPriorityUpdateEnd will be called last. If the frame has an empty
+  //     Priority Field Value, then this will be called immediately after
+  //     OnPriorityUpdateStart.
+  virtual void OnPriorityUpdateStart(
+      const Http2FrameHeader& header,
+      const Http2PriorityUpdateFields& priority_update) = 0;
+
+  // Called when the next portion of a PRIORITY_UPDATE frame's Priority Field
+  // Value field is received.
+  // |data| The start of |len| bytes of data.
+  // |len| The length of the data buffer. May be zero in some cases, which does
+  //     not mean anything special.
+  virtual void OnPriorityUpdatePayload(const char* data, size_t len) = 0;
+
+  // Called after an entire PRIORITY_UPDATE frame has been received.
+  virtual void OnPriorityUpdateEnd() = 0;
+
   // Called when the common frame header has been decoded, but the frame type
   // is unknown, after which:
   //   OnUnknownPayload is called as the payload of the frame is provided to the
@@ -341,6 +363,11 @@
   void OnAltSvcOriginData(const char* /*data*/, size_t /*len*/) override {}
   void OnAltSvcValueData(const char* /*data*/, size_t /*len*/) override {}
   void OnAltSvcEnd() override {}
+  void OnPriorityUpdateStart(
+      const Http2FrameHeader& /*header*/,
+      const Http2PriorityUpdateFields& /*priority_update*/) override {}
+  void OnPriorityUpdatePayload(const char* /*data*/, size_t /*len*/) override {}
+  void OnPriorityUpdateEnd() override {}
   void OnUnknownStart(const Http2FrameHeader& /*header*/) override {}
   void OnUnknownPayload(const char* /*data*/, size_t /*len*/) override {}
   void OnUnknownEnd() override {}
diff --git a/http2/decoder/http2_frame_decoder_listener_test_util.cc b/http2/decoder/http2_frame_decoder_listener_test_util.cc
index e35caf6..ddd0064 100644
--- a/http2/decoder/http2_frame_decoder_listener_test_util.cc
+++ b/http2/decoder/http2_frame_decoder_listener_test_util.cc
@@ -168,6 +168,23 @@
   FAIL() << "OnAltSvcEnd";
 }
 
+void FailingHttp2FrameDecoderListener::OnPriorityUpdateStart(
+    const Http2FrameHeader& header,
+    const Http2PriorityUpdateFields& priority_update) {
+  FAIL() << "OnPriorityUpdateStart: " << header << "; prioritized_stream_id: "
+         << priority_update.prioritized_stream_id;
+}
+
+void FailingHttp2FrameDecoderListener::OnPriorityUpdatePayload(
+    const char* /*data*/,
+    size_t len) {
+  FAIL() << "OnPriorityUpdatePayload: len=" << len;
+}
+
+void FailingHttp2FrameDecoderListener::OnPriorityUpdateEnd() {
+  FAIL() << "OnPriorityUpdateEnd";
+}
+
 void FailingHttp2FrameDecoderListener::OnUnknownStart(
     const Http2FrameHeader& header) {
   FAIL() << "OnUnknownStart: " << header;
@@ -445,6 +462,30 @@
   }
 }
 
+void LoggingHttp2FrameDecoderListener::OnPriorityUpdateStart(
+    const Http2FrameHeader& header,
+    const Http2PriorityUpdateFields& priority_update) {
+  HTTP2_VLOG(1) << "OnPriorityUpdateStart";
+  if (wrapped_ != nullptr) {
+    wrapped_->OnPriorityUpdateStart(header, priority_update);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPriorityUpdatePayload(const char* data,
+                                                               size_t len) {
+  HTTP2_VLOG(1) << "OnPriorityUpdatePayload";
+  if (wrapped_ != nullptr) {
+    wrapped_->OnPriorityUpdatePayload(data, len);
+  }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPriorityUpdateEnd() {
+  HTTP2_VLOG(1) << "OnPriorityUpdateEnd";
+  if (wrapped_ != nullptr) {
+    wrapped_->OnPriorityUpdateEnd();
+  }
+}
+
 void LoggingHttp2FrameDecoderListener::OnUnknownStart(
     const Http2FrameHeader& header) {
   HTTP2_VLOG(1) << "OnUnknownStart: " << header;
diff --git a/http2/decoder/http2_frame_decoder_listener_test_util.h b/http2/decoder/http2_frame_decoder_listener_test_util.h
index da5a87c..e748327 100644
--- a/http2/decoder/http2_frame_decoder_listener_test_util.h
+++ b/http2/decoder/http2_frame_decoder_listener_test_util.h
@@ -64,6 +64,11 @@
   void OnAltSvcOriginData(const char* data, size_t len) override;
   void OnAltSvcValueData(const char* data, size_t len) override;
   void OnAltSvcEnd() override;
+  void OnPriorityUpdateStart(
+      const Http2FrameHeader& header,
+      const Http2PriorityUpdateFields& priority_update) override;
+  void OnPriorityUpdatePayload(const char* data, size_t len) override;
+  void OnPriorityUpdateEnd() override;
   void OnUnknownStart(const Http2FrameHeader& header) override;
   void OnUnknownPayload(const char* data, size_t len) override;
   void OnUnknownEnd() override;
@@ -125,6 +130,11 @@
   void OnAltSvcOriginData(const char* data, size_t len) override;
   void OnAltSvcValueData(const char* data, size_t len) override;
   void OnAltSvcEnd() override;
+  void OnPriorityUpdateStart(
+      const Http2FrameHeader& header,
+      const Http2PriorityUpdateFields& priority_update) override;
+  void OnPriorityUpdatePayload(const char* data, size_t len) override;
+  void OnPriorityUpdateEnd() override;
   void OnUnknownStart(const Http2FrameHeader& header) override;
   void OnUnknownPayload(const char* data, size_t len) override;
   void OnUnknownEnd() override;
diff --git a/http2/decoder/http2_frame_decoder_test.cc b/http2/decoder/http2_frame_decoder_test.cc
index 883c84b..0b0b415 100644
--- a/http2/decoder/http2_frame_decoder_test.cc
+++ b/http2/decoder/http2_frame_decoder_test.cc
@@ -11,6 +11,7 @@
 
 #include "absl/strings/string_view.h"
 #include "http2/http2_constants.h"
+#include "http2/platform/api/http2_flags.h"
 #include "http2/platform/api/http2_logging.h"
 #include "http2/platform/api/http2_test_helpers.h"
 #include "http2/test_tools/frame_parts.h"
@@ -546,6 +547,29 @@
   EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
 }
 
+TEST_F(Http2FrameDecoderTest, PriorityUpdatePayload) {
+  const char kFrameData[] = {
+      '\x00', '\x00', '\x07',          // Payload length: 7
+      '\x10',                          // PRIORITY_UPDATE
+      '\x00',                          // Flags: none
+      '\x00', '\x00', '\x00', '\x00',  // Stream ID: 0
+      '\x00', '\x00', '\x00', '\x05',  // Prioritized Stream ID: 5
+      'a',    'b',    'c',             // Priority Field Value
+  };
+  Http2FrameHeader header(7, Http2FrameType::PRIORITY_UPDATE, 0, 0);
+
+  if (GetHttp2RestartFlag(http2_parse_priority_update_frame)) {
+    FrameParts expected(header, "abc");
+    expected.SetOptPriorityUpdate(Http2PriorityUpdateFields{5});
+    EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+  } else {
+    FrameParts expected(header, absl::string_view("\x00\x00\x00\x05"
+                                                  "abc",
+                                                  7));
+    EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+  }
+}
+
 TEST_F(Http2FrameDecoderTest, UnknownPayload) {
   const char kFrameData[] = {
       '\x00', '\x00', '\x03',          // Payload length: 3
diff --git a/http2/decoder/payload_decoders/priority_update_payload_decoder.cc b/http2/decoder/payload_decoders/priority_update_payload_decoder.cc
new file mode 100644
index 0000000..ec52c20
--- /dev/null
+++ b/http2/decoder/payload_decoders/priority_update_payload_decoder.cc
@@ -0,0 +1,125 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "http2/decoder/payload_decoders/priority_update_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "http2/decoder/decode_buffer.h"
+#include "http2/decoder/http2_frame_decoder_listener.h"
+#include "http2/http2_constants.h"
+#include "http2/http2_structures.h"
+#include "http2/platform/api/http2_bug_tracker.h"
+#include "http2/platform/api/http2_logging.h"
+#include "http2/platform/api/http2_macros.h"
+
+namespace http2 {
+
+std::ostream& operator<<(std::ostream& out,
+                         PriorityUpdatePayloadDecoder::PayloadState v) {
+  switch (v) {
+    case PriorityUpdatePayloadDecoder::PayloadState::kStartDecodingFixedFields:
+      return out << "kStartDecodingFixedFields";
+    case PriorityUpdatePayloadDecoder::PayloadState::kResumeDecodingFixedFields:
+      return out << "kResumeDecodingFixedFields";
+    case PriorityUpdatePayloadDecoder::PayloadState::kHandleFixedFieldsStatus:
+      return out << "kHandleFixedFieldsStatus";
+    case PriorityUpdatePayloadDecoder::PayloadState::kReadPriorityFieldValue:
+      return out << "kReadPriorityFieldValue";
+  }
+  // Since the value doesn't come over the wire, only a programming bug should
+  // result in reaching this point.
+  int unknown = static_cast<int>(v);
+  HTTP2_BUG << "Invalid PriorityUpdatePayloadDecoder::PayloadState: "
+            << unknown;
+  return out << "PriorityUpdatePayloadDecoder::PayloadState(" << unknown << ")";
+}
+
+DecodeStatus PriorityUpdatePayloadDecoder::StartDecodingPayload(
+    FrameDecoderState* state,
+    DecodeBuffer* db) {
+  HTTP2_DVLOG(2) << "PriorityUpdatePayloadDecoder::StartDecodingPayload: "
+                 << state->frame_header();
+  DCHECK_EQ(Http2FrameType::PRIORITY_UPDATE, state->frame_header().type);
+  DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+  DCHECK_EQ(0, state->frame_header().flags);
+
+  state->InitializeRemainders();
+  payload_state_ = PayloadState::kStartDecodingFixedFields;
+  return ResumeDecodingPayload(state, db);
+}
+
+DecodeStatus PriorityUpdatePayloadDecoder::ResumeDecodingPayload(
+    FrameDecoderState* state,
+    DecodeBuffer* db) {
+  HTTP2_DVLOG(2) << "PriorityUpdatePayloadDecoder::ResumeDecodingPayload: "
+                    "remaining_payload="
+                 << state->remaining_payload()
+                 << ", db->Remaining=" << db->Remaining();
+
+  const Http2FrameHeader& frame_header = state->frame_header();
+  DCHECK_EQ(Http2FrameType::PRIORITY_UPDATE, frame_header.type);
+  DCHECK_LE(db->Remaining(), frame_header.payload_length);
+  DCHECK_NE(PayloadState::kHandleFixedFieldsStatus, payload_state_);
+
+  // |status| has to be initialized to some value to avoid compiler error in
+  // case PayloadState::kHandleFixedFieldsStatus below, but value does not
+  // matter, see DCHECK_NE above.
+  DecodeStatus status = DecodeStatus::kDecodeError;
+  size_t avail;
+  while (true) {
+    HTTP2_DVLOG(2)
+        << "PriorityUpdatePayloadDecoder::ResumeDecodingPayload payload_state_="
+        << payload_state_;
+    switch (payload_state_) {
+      case PayloadState::kStartDecodingFixedFields:
+        status = state->StartDecodingStructureInPayload(
+            &priority_update_fields_, db);
+        HTTP2_FALLTHROUGH;
+
+      case PayloadState::kHandleFixedFieldsStatus:
+        if (status == DecodeStatus::kDecodeDone) {
+          state->listener()->OnPriorityUpdateStart(frame_header,
+                                                   priority_update_fields_);
+        } else {
+          // Not done decoding the structure. Either we've got more payload
+          // to decode, or we've run out because the payload is too short,
+          // in which case OnFrameSizeError will have already been called.
+          DCHECK((status == DecodeStatus::kDecodeInProgress &&
+                  state->remaining_payload() > 0) ||
+                 (status == DecodeStatus::kDecodeError &&
+                  state->remaining_payload() == 0))
+              << "\n status=" << status
+              << "; remaining_payload=" << state->remaining_payload();
+          payload_state_ = PayloadState::kResumeDecodingFixedFields;
+          return status;
+        }
+        HTTP2_FALLTHROUGH;
+
+      case PayloadState::kReadPriorityFieldValue:
+        // Anything left in the decode buffer is the Priority Field Value.
+        avail = db->Remaining();
+        if (avail > 0) {
+          state->listener()->OnPriorityUpdatePayload(db->cursor(), avail);
+          db->AdvanceCursor(avail);
+          state->ConsumePayload(avail);
+        }
+        if (state->remaining_payload() > 0) {
+          payload_state_ = PayloadState::kReadPriorityFieldValue;
+          return DecodeStatus::kDecodeInProgress;
+        }
+        state->listener()->OnPriorityUpdateEnd();
+        return DecodeStatus::kDecodeDone;
+
+      case PayloadState::kResumeDecodingFixedFields:
+        status = state->ResumeDecodingStructureInPayload(
+            &priority_update_fields_, db);
+        payload_state_ = PayloadState::kHandleFixedFieldsStatus;
+        continue;
+    }
+    HTTP2_BUG << "PayloadState: " << payload_state_;
+  }
+}
+
+}  // namespace http2
diff --git a/http2/decoder/payload_decoders/priority_update_payload_decoder.h b/http2/decoder/payload_decoders/priority_update_payload_decoder.h
new file mode 100644
index 0000000..f2557b2
--- /dev/null
+++ b/http2/decoder/payload_decoders/priority_update_payload_decoder.h
@@ -0,0 +1,63 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PRIORITY_UPDATE_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PRIORITY_UPDATE_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a PRIORITY_UPDATE frame.
+
+#include "http2/decoder/decode_buffer.h"
+#include "http2/decoder/decode_status.h"
+#include "http2/decoder/frame_decoder_state.h"
+#include "http2/http2_structures.h"
+#include "common/platform/api/quiche_export.h"
+
+namespace http2 {
+namespace test {
+class PriorityUpdatePayloadDecoderPeer;
+}  // namespace test
+
+class QUICHE_EXPORT_PRIVATE PriorityUpdatePayloadDecoder {
+ public:
+  // States during decoding of a PRIORITY_UPDATE frame.
+  enum class PayloadState {
+    // At the start of the PRIORITY_UPDATE frame payload, ready to start
+    // decoding the fixed size fields into priority_update_fields_.
+    kStartDecodingFixedFields,
+
+    // The fixed size fields weren't all available when the decoder first
+    // tried to decode them; this state resumes the decoding when
+    // ResumeDecodingPayload is called later.
+    kResumeDecodingFixedFields,
+
+    // Handle the DecodeStatus returned from starting or resuming the decoding
+    // of Http2PriorityUpdateFields into priority_update_fields_. If complete,
+    // calls OnPriorityUpdateStart.
+    kHandleFixedFieldsStatus,
+
+    // Report the Priority Field Value portion of the payload to the listener's
+    // OnPriorityUpdatePayload method, and call OnPriorityUpdateEnd when the end
+    // of the payload is reached.
+    kReadPriorityFieldValue,
+  };
+
+  // Starts the decoding of a PRIORITY_UPDATE frame's payload, and completes it
+  // if the entire payload is in the provided decode buffer.
+  DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+  // Resumes decoding a PRIORITY_UPDATE frame that has been split across decode
+  // buffers.
+  DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+                                     DecodeBuffer* db);
+
+ private:
+  friend class test::PriorityUpdatePayloadDecoderPeer;
+
+  Http2PriorityUpdateFields priority_update_fields_;
+  PayloadState payload_state_;
+};
+
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PRIORITY_UPDATE_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/priority_update_payload_decoder_test.cc b/http2/decoder/payload_decoders/priority_update_payload_decoder_test.cc
new file mode 100644
index 0000000..39d0afc
--- /dev/null
+++ b/http2/decoder/payload_decoders/priority_update_payload_decoder_test.cc
@@ -0,0 +1,135 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "http2/decoder/payload_decoders/priority_update_payload_decoder.h"
+
+#include <stddef.h>
+
+#include <string>
+
+#include "http2/decoder/http2_frame_decoder_listener.h"
+#include "http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "http2/http2_constants.h"
+#include "http2/http2_structures_test_util.h"
+#include "http2/platform/api/http2_logging.h"
+#include "http2/test_tools/frame_parts.h"
+#include "http2/test_tools/frame_parts_collector.h"
+#include "http2/test_tools/http2_random.h"
+#include "http2/tools/http2_frame_builder.h"
+#include "http2/tools/random_decoder_test.h"
+#include "common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace test {
+
+class PriorityUpdatePayloadDecoderPeer {
+ public:
+  static constexpr Http2FrameType FrameType() {
+    return Http2FrameType::PRIORITY_UPDATE;
+  }
+
+  // Returns the mask of flags that affect the decoding of the payload (i.e.
+  // flags that that indicate the presence of certain fields or padding).
+  static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; }
+};
+
+namespace {
+
+struct Listener : public FramePartsCollector {
+  void OnPriorityUpdateStart(
+      const Http2FrameHeader& header,
+      const Http2PriorityUpdateFields& priority_update) override {
+    HTTP2_VLOG(1) << "OnPriorityUpdateStart header: " << header
+                  << "; priority_update: " << priority_update;
+    StartFrame(header)->OnPriorityUpdateStart(header, priority_update);
+  }
+
+  void OnPriorityUpdatePayload(const char* data, size_t len) override {
+    HTTP2_VLOG(1) << "OnPriorityUpdatePayload: len=" << len;
+    CurrentFrame()->OnPriorityUpdatePayload(data, len);
+  }
+
+  void OnPriorityUpdateEnd() override {
+    HTTP2_VLOG(1) << "OnPriorityUpdateEnd";
+    EndFrame()->OnPriorityUpdateEnd();
+  }
+
+  void OnFrameSizeError(const Http2FrameHeader& header) override {
+    HTTP2_VLOG(1) << "OnFrameSizeError: " << header;
+    FrameError(header)->OnFrameSizeError(header);
+  }
+};
+
+// Avoid initialization of test class when flag is false, because base class
+// method AbstractPayloadDecoderTest::SetUp() crashes if
+// IsSupportedHttp2FrameType(PRIORITY_UPDATE) returns false.
+std::vector<bool> GetTestParams() {
+  if (GetHttp2RestartFlag(http2_parse_priority_update_frame)) {
+    return {true};  // Actual Boolean value is ignored.
+  } else {
+    return {};
+  }
+}
+
+class PriorityUpdatePayloadDecoderTest
+    : public AbstractPayloadDecoderTest<PriorityUpdatePayloadDecoder,
+                                        PriorityUpdatePayloadDecoderPeer,
+                                        Listener>,
+      public ::testing::WithParamInterface<bool> {};
+
+INSTANTIATE_TEST_SUITE_P(MaybeRunTest,
+                         PriorityUpdatePayloadDecoderTest,
+                         ::testing::ValuesIn(GetTestParams()));
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PriorityUpdatePayloadDecoderTest);
+
+// Confirm we get an error if the payload is not long enough to hold
+// Http2PriorityUpdateFields.
+TEST_P(PriorityUpdatePayloadDecoderTest, Truncated) {
+  auto approve_size = [](size_t size) {
+    return size != Http2PriorityUpdateFields::EncodedSize();
+  };
+  Http2FrameBuilder fb;
+  fb.Append(Http2PriorityUpdateFields(123));
+  EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size));
+}
+
+class PriorityUpdatePayloadLengthTests
+    : public AbstractPayloadDecoderTest<PriorityUpdatePayloadDecoder,
+                                        PriorityUpdatePayloadDecoderPeer,
+                                        Listener>,
+      public ::testing::WithParamInterface<std::tuple<uint32_t, bool>> {
+ protected:
+  PriorityUpdatePayloadLengthTests() : length_(std::get<0>(GetParam())) {
+    HTTP2_VLOG(1) << "################  length_=" << length_
+                  << "  ################";
+  }
+
+  const uint32_t length_;
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    VariousLengths,
+    PriorityUpdatePayloadLengthTests,
+    ::testing::Combine(::testing::Values(0, 1, 2, 3, 4, 5, 6),
+                       ::testing::ValuesIn(GetTestParams())));
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PriorityUpdatePayloadLengthTests);
+
+TEST_P(PriorityUpdatePayloadLengthTests, ValidLength) {
+  Http2PriorityUpdateFields priority_update;
+  Randomize(&priority_update, RandomPtr());
+  std::string priority_field_value = Random().RandString(length_);
+  Http2FrameBuilder fb;
+  fb.Append(priority_update);
+  fb.Append(priority_field_value);
+  Http2FrameHeader header(fb.size(), Http2FrameType::PRIORITY_UPDATE,
+                          RandFlags(), RandStreamId());
+  set_frame_header(header);
+  FrameParts expected(header, priority_field_value);
+  expected.SetOptPriorityUpdate(Http2PriorityUpdateFields{priority_update});
+  ASSERT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace http2