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
diff --git a/http2/http2_constants.cc b/http2/http2_constants.cc
index 0c4a587..1cc6b17 100644
--- a/http2/http2_constants.cc
+++ b/http2/http2_constants.cc
@@ -35,6 +35,8 @@
       return "CONTINUATION";
     case Http2FrameType::ALTSVC:
       return "ALTSVC";
+    case Http2FrameType::PRIORITY_UPDATE:
+      return "PRIORITY_UPDATE";
   }
   return absl::StrCat("UnknownFrameType(", static_cast<int>(v), ")");
 }
diff --git a/http2/http2_constants.h b/http2/http2_constants.h
index f3073ac..cd05eb4 100644
--- a/http2/http2_constants.h
+++ b/http2/http2_constants.h
@@ -12,6 +12,7 @@
 #include <ostream>
 #include <string>
 
+#include "http2/platform/api/http2_flags.h"
 #include "common/platform/api/quiche_export.h"
 
 namespace http2 {
@@ -28,8 +29,6 @@
 
 // The value used to identify types of frames. Upper case to match the RFC.
 // The comments indicate which flags are valid for that frame type.
-// ALTSVC is defined in http://httpwg.org/http-extensions/alt-svc.html
-// (not yet final standard as of March 2016, but close).
 enum class Http2FrameType : uint8_t {
   DATA = 0,           // END_STREAM | PADDED
   HEADERS = 1,        // END_STREAM | END_HEADERS | PADDED | PRIORITY
@@ -41,11 +40,19 @@
   GOAWAY = 7,         //
   WINDOW_UPDATE = 8,  //
   CONTINUATION = 9,   // END_HEADERS
-  ALTSVC = 10,        //
+  // https://tools.ietf.org/html/rfc7838
+  ALTSVC = 10,  // no flags
+  // https://tools.ietf.org/html/draft-ietf-httpbis-priority-02
+  PRIORITY_UPDATE = 16,  // no flags
 };
 
 // Is the frame type known/supported?
 inline bool IsSupportedHttp2FrameType(uint32_t v) {
+  if (GetHttp2RestartFlag(http2_parse_priority_update_frame) &&
+      v == static_cast<uint32_t>(Http2FrameType::PRIORITY_UPDATE)) {
+    return true;
+  }
+
   return v <= static_cast<uint32_t>(Http2FrameType::ALTSVC);
 }
 inline bool IsSupportedHttp2FrameType(Http2FrameType v) {
diff --git a/http2/http2_structures.cc b/http2/http2_structures.cc
index f9b2405..dda0dd8 100644
--- a/http2/http2_structures.cc
+++ b/http2/http2_structures.cc
@@ -130,4 +130,22 @@
   return out << "origin_length=" << v.origin_length;
 }
 
+// Http2PriorityUpdateFields:
+
+bool operator==(const Http2PriorityUpdateFields& a,
+                const Http2PriorityUpdateFields& b) {
+  return a.prioritized_stream_id == b.prioritized_stream_id;
+}
+
+std::string Http2PriorityUpdateFields::ToString() const {
+  std::stringstream ss;
+  ss << "prioritized_stream_id=" << prioritized_stream_id;
+  return ss.str();
+}
+
+std::ostream& operator<<(std::ostream& out,
+                         const Http2PriorityUpdateFields& v) {
+  return out << v.ToString();
+}
+
 }  // namespace http2
diff --git a/http2/http2_structures.h b/http2/http2_structures.h
index f4b9030..79f739d 100644
--- a/http2/http2_structures.h
+++ b/http2/http2_structures.h
@@ -321,6 +321,32 @@
 QUICHE_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
                                                const Http2AltSvcFields& v);
 
+// Http2PriorityUpdateFields:
+
+struct QUICHE_EXPORT_PRIVATE Http2PriorityUpdateFields {
+  Http2PriorityUpdateFields() {}
+  Http2PriorityUpdateFields(uint32_t prioritized_stream_id)
+      : prioritized_stream_id(prioritized_stream_id) {}
+  static constexpr size_t EncodedSize() { return 4; }
+
+  // Produce strings useful for debugging/logging messages.
+  std::string ToString() const;
+
+  // The 31-bit stream identifier of the stream whose priority is updated.
+  uint32_t prioritized_stream_id;
+};
+
+QUICHE_EXPORT_PRIVATE bool operator==(const Http2PriorityUpdateFields& a,
+                                      const Http2PriorityUpdateFields& b);
+QUICHE_EXPORT_PRIVATE inline bool operator!=(
+    const Http2PriorityUpdateFields& a,
+    const Http2PriorityUpdateFields& b) {
+  return !(a == b);
+}
+QUICHE_EXPORT_PRIVATE std::ostream& operator<<(
+    std::ostream& out,
+    const Http2PriorityUpdateFields& v);
+
 }  // namespace http2
 
 #endif  // QUICHE_HTTP2_HTTP2_STRUCTURES_H_
diff --git a/http2/http2_structures_test.cc b/http2/http2_structures_test.cc
index b29d4b6..3525c40 100644
--- a/http2/http2_structures_test.cc
+++ b/http2/http2_structures_test.cc
@@ -535,6 +535,27 @@
   EXPECT_TRUE(VerifyRandomCalls<Http2AltSvcFields>());
 }
 
+TEST(Http2PriorityUpdateFieldsTest, Eq) {
+  Http2PriorityUpdateFields u(/* prioritized_stream_id = */ 1);
+  Http2PriorityUpdateFields v(/* prioritized_stream_id = */ 3);
+
+  EXPECT_NE(u, v);
+  EXPECT_FALSE(u == v);
+  EXPECT_TRUE(u != v);
+
+  u = v;
+  EXPECT_EQ(u, v);
+  EXPECT_TRUE(u == v);
+  EXPECT_FALSE(u != v);
+}
+
+TEST(Http2PriorityUpdateFieldsTest, Misc) {
+  Http2PriorityUpdateFields u(/* prioritized_stream_id = */ 1);
+  EXPECT_EQ("prioritized_stream_id=1", u.ToString());
+
+  EXPECT_TRUE(VerifyRandomCalls<Http2PriorityUpdateFields>());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace http2
diff --git a/http2/http2_structures_test_util.cc b/http2/http2_structures_test_util.cc
index ecdbadc..83b5a95 100644
--- a/http2/http2_structures_test_util.cc
+++ b/http2/http2_structures_test_util.cc
@@ -50,6 +50,9 @@
 void Randomize(Http2AltSvcFields* out, Http2Random* rng) {
   out->origin_length = rng->Rand16();
 }
+void Randomize(Http2PriorityUpdateFields* out, Http2Random* rng) {
+  out->prioritized_stream_id = rng->Rand32() & StreamIdMask();
+}
 
 void ScrubFlagsOfHeader(Http2FrameHeader* header) {
   uint8_t invalid_mask = InvalidFlagMaskForFrameType(header->type);
diff --git a/http2/http2_structures_test_util.h b/http2/http2_structures_test_util.h
index 81bf6bf..31c1f14 100644
--- a/http2/http2_structures_test_util.h
+++ b/http2/http2_structures_test_util.h
@@ -34,6 +34,7 @@
 void Randomize(Http2GoAwayFields* out, Http2Random* rng);
 void Randomize(Http2WindowUpdateFields* out, Http2Random* rng);
 void Randomize(Http2AltSvcFields* out, Http2Random* rng);
+void Randomize(Http2PriorityUpdateFields* out, Http2Random* rng);
 
 // Clear bits of header->flags that are known to be invalid for the
 // type. For unknown frame types, no change is made.
diff --git a/http2/test_tools/frame_parts.cc b/http2/test_tools/frame_parts.cc
index f4fd391..aea23a9 100644
--- a/http2/test_tools/frame_parts.cc
+++ b/http2/test_tools/frame_parts.cc
@@ -84,6 +84,7 @@
 
   VERIFY_OPTIONAL_FIELD(opt_altsvc_origin_length_) << COMMON_MESSAGE;
   VERIFY_OPTIONAL_FIELD(opt_altsvc_value_length_) << COMMON_MESSAGE;
+  VERIFY_OPTIONAL_FIELD(opt_priority_update_) << COMMON_MESSAGE;
   VERIFY_OPTIONAL_FIELD(opt_goaway_) << COMMON_MESSAGE;
   VERIFY_OPTIONAL_FIELD(opt_missing_length_) << COMMON_MESSAGE;
   VERIFY_OPTIONAL_FIELD(opt_pad_length_) << COMMON_MESSAGE;
@@ -364,6 +365,31 @@
   ASSERT_TRUE(EndFrameOfType(Http2FrameType::ALTSVC)) << *this;
 }
 
+void FrameParts::OnPriorityUpdateStart(
+    const Http2FrameHeader& header,
+    const Http2PriorityUpdateFields& priority_update) {
+  HTTP2_VLOG(1) << "OnPriorityUpdateStart: " << header
+                << "    prioritized_stream_id: "
+                << priority_update.prioritized_stream_id;
+  ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::PRIORITY_UPDATE))
+      << *this;
+  ASSERT_FALSE(opt_priority_update_);
+  opt_priority_update_ = priority_update;
+  opt_payload_length_ =
+      header.payload_length - Http2PriorityUpdateFields::EncodedSize();
+}
+
+void FrameParts::OnPriorityUpdatePayload(const char* data, size_t len) {
+  HTTP2_VLOG(1) << "OnPriorityUpdatePayload: len=" << len;
+  ASSERT_TRUE(InFrameOfType(Http2FrameType::PRIORITY_UPDATE)) << *this;
+  payload_.append(absl::string_view(data, len));
+}
+
+void FrameParts::OnPriorityUpdateEnd() {
+  HTTP2_VLOG(1) << "OnPriorityUpdateEnd; frame_header_: " << frame_header_;
+  ASSERT_TRUE(EndFrameOfType(Http2FrameType::PRIORITY_UPDATE)) << *this;
+}
+
 void FrameParts::OnUnknownStart(const Http2FrameHeader& header) {
   HTTP2_VLOG(1) << "OnUnknownStart: " << header;
   ASSERT_FALSE(IsSupportedHttp2FrameType(header.type)) << header;
@@ -460,6 +486,9 @@
   if (opt_altsvc_value_length_) {
     out << "  value_length=" << opt_altsvc_value_length_.value() << "\n";
   }
+  if (opt_priority_update_) {
+    out << "  prioritized_stream_id_=" << opt_priority_update_.value() << "\n";
+  }
   if (has_frame_size_error_) {
     out << "  has_frame_size_error\n";
   }
diff --git a/http2/test_tools/frame_parts.h b/http2/test_tools/frame_parts.h
index 9e34fb9..1cc6928 100644
--- a/http2/test_tools/frame_parts.h
+++ b/http2/test_tools/frame_parts.h
@@ -101,6 +101,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;
@@ -180,6 +185,10 @@
       absl::optional<size_t> opt_window_update_increment) {
     opt_window_update_increment_ = opt_window_update_increment;
   }
+  void SetOptPriorityUpdate(
+      absl::optional<Http2PriorityUpdateFields> priority_update) {
+    opt_priority_update_ = priority_update;
+  }
 
   void SetHasFrameSizeError(bool has_frame_size_error) {
     has_frame_size_error_ = has_frame_size_error;
@@ -223,6 +232,7 @@
   absl::optional<Http2PushPromiseFields> opt_push_promise_;
   absl::optional<Http2PingFields> opt_ping_;
   absl::optional<Http2GoAwayFields> opt_goaway_;
+  absl::optional<Http2PriorityUpdateFields> opt_priority_update_;
 
   absl::optional<size_t> opt_pad_length_;
   absl::optional<size_t> opt_payload_length_;
diff --git a/http2/test_tools/frame_parts_collector_listener.cc b/http2/test_tools/frame_parts_collector_listener.cc
index 4326f04..b185d8e 100644
--- a/http2/test_tools/frame_parts_collector_listener.cc
+++ b/http2/test_tools/frame_parts_collector_listener.cc
@@ -196,6 +196,25 @@
   EndFrame()->OnAltSvcEnd();
 }
 
+void FramePartsCollectorListener::OnPriorityUpdateStart(
+    const Http2FrameHeader& header,
+    const Http2PriorityUpdateFields& priority_update) {
+  HTTP2_VLOG(1) << "OnPriorityUpdateStart header: " << header
+                << "; priority_update=" << priority_update;
+  StartFrame(header)->OnPriorityUpdateStart(header, priority_update);
+}
+
+void FramePartsCollectorListener::OnPriorityUpdatePayload(const char* data,
+                                                          size_t len) {
+  HTTP2_VLOG(1) << "OnPriorityUpdatePayload: len=" << len;
+  CurrentFrame()->OnPriorityUpdatePayload(data, len);
+}
+
+void FramePartsCollectorListener::OnPriorityUpdateEnd() {
+  HTTP2_VLOG(1) << "OnPriorityUpdateEnd";
+  EndFrame()->OnPriorityUpdateEnd();
+}
+
 void FramePartsCollectorListener::OnUnknownStart(
     const Http2FrameHeader& header) {
   HTTP2_VLOG(1) << "OnUnknownStart: " << header;
diff --git a/http2/test_tools/frame_parts_collector_listener.h b/http2/test_tools/frame_parts_collector_listener.h
index 84792ac..bb09bf8 100644
--- a/http2/test_tools/frame_parts_collector_listener.h
+++ b/http2/test_tools/frame_parts_collector_listener.h
@@ -71,6 +71,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/tools/http2_frame_builder.cc b/http2/tools/http2_frame_builder.cc
index 9bb79e1..d6cd110 100644
--- a/http2/tools/http2_frame_builder.cc
+++ b/http2/tools/http2_frame_builder.cc
@@ -141,6 +141,10 @@
   AppendUInt16(v.origin_length);
 }
 
+void Http2FrameBuilder::Append(const Http2PriorityUpdateFields& v) {
+  AppendUInt31(v.prioritized_stream_id);
+}
+
 // Methods for changing existing buffer contents.
 
 void Http2FrameBuilder::WriteAt(absl::string_view s, size_t offset) {
diff --git a/http2/tools/http2_frame_builder.h b/http2/tools/http2_frame_builder.h
index 2ed37d5..a40e46d 100644
--- a/http2/tools/http2_frame_builder.h
+++ b/http2/tools/http2_frame_builder.h
@@ -76,6 +76,7 @@
   void Append(const Http2GoAwayFields& v);
   void Append(const Http2WindowUpdateFields& v);
   void Append(const Http2AltSvcFields& v);
+  void Append(const Http2PriorityUpdateFields& v);
 
   // Methods for changing existing buffer contents (mostly focused on updating
   // the payload length).
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index baf8ee9..0e149b1 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -69,6 +69,7 @@
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_use_write_or_buffer_data_at_level, false)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_send_quic_fallback_server_config_on_leto_error, false)
 QUIC_FLAG(FLAGS_quic_restart_flag_dont_fetch_quic_private_keys_from_leto, false)
+QUIC_FLAG(FLAGS_quic_restart_flag_http2_parse_priority_update_frame, false)
 QUIC_FLAG(FLAGS_quic_restart_flag_quic_dispatcher_support_multiple_cid_per_connection, false)
 QUIC_FLAG(FLAGS_quic_restart_flag_quic_enable_zero_rtt_for_tls_v2, true)
 QUIC_FLAG(FLAGS_quic_restart_flag_quic_offload_pacing_to_usps2, false)
diff --git a/spdy/core/http2_frame_decoder_adapter.cc b/spdy/core/http2_frame_decoder_adapter.cc
index 379d221..376c3c1 100644
--- a/spdy/core/http2_frame_decoder_adapter.cc
+++ b/spdy/core/http2_frame_decoder_adapter.cc
@@ -717,6 +717,33 @@
   alt_svc_value_.shrink_to_fit();
 }
 
+void Http2DecoderAdapter::OnPriorityUpdateStart(
+    const Http2FrameHeader& header,
+    const Http2PriorityUpdateFields& priority_update) {
+  SPDY_DVLOG(1) << "OnPriorityUpdateStart: " << header
+                << "; prioritized_stream_id: "
+                << priority_update.prioritized_stream_id;
+  if (IsOkToStartFrame(header) && HasRequiredStreamIdZero(header) &&
+      HasRequiredStreamId(priority_update.prioritized_stream_id)) {
+    frame_header_ = header;
+    has_frame_header_ = true;
+    prioritized_stream_id_ = priority_update.prioritized_stream_id;
+  }
+}
+
+void Http2DecoderAdapter::OnPriorityUpdatePayload(const char* data,
+                                                  size_t len) {
+  SPDY_DVLOG(1) << "OnPriorityUpdatePayload: len=" << len;
+  priority_field_value_.append(data, len);
+}
+
+void Http2DecoderAdapter::OnPriorityUpdateEnd() {
+  SPDY_DVLOG(1) << "OnPriorityUpdateEnd: priority_field_value.size(): "
+                << priority_field_value_.size();
+  visitor()->OnPriorityUpdate(prioritized_stream_id_, priority_field_value_);
+  priority_field_value_.clear();
+}
+
 // Except for BLOCKED frames, all other unknown frames are either dropped or
 // passed to a registered extension.
 void Http2DecoderAdapter::OnUnknownStart(const Http2FrameHeader& header) {
diff --git a/spdy/core/http2_frame_decoder_adapter.h b/spdy/core/http2_frame_decoder_adapter.h
index 1192bba..44f3800 100644
--- a/spdy/core/http2_frame_decoder_adapter.h
+++ b/spdy/core/http2_frame_decoder_adapter.h
@@ -209,6 +209,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;
@@ -275,6 +280,10 @@
   std::string alt_svc_origin_;
   std::string alt_svc_value_;
 
+  // Temporary buffers for PRIORITY_UPDATE fields.
+  uint32_t prioritized_stream_id_ = 0;
+  std::string priority_field_value_;
+
   // Listener used if we transition to an error state; the listener ignores all
   // the callbacks.
   Http2FrameDecoderNoOpListener no_op_listener_;
diff --git a/spdy/core/spdy_framer_test.cc b/spdy/core/spdy_framer_test.cc
index 6310e32..3da8dbc 100644
--- a/spdy/core/spdy_framer_test.cc
+++ b/spdy/core/spdy_framer_test.cc
@@ -4517,6 +4517,149 @@
              deframer_.spdy_framer_error());
 }
 
+TEST_P(SpdyFramerTest, ReadPriorityUpdateFrame) {
+  const char kFrameData[] = {
+      0x00, 0x00, 0x07,        // payload length
+      0x10,                    // frame type PRIORITY_UPDATE
+      0x00,                    // flags
+      0x00, 0x00, 0x00, 0x00,  // stream ID, must be 0
+      0x00, 0x00, 0x00, 0x03,  // prioritized stream ID, must not be zero
+      'f',  'o',  'o'          // priority field value
+  };
+
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+  deframer_.set_visitor(&visitor);
+
+  if (GetHttp2RestartFlag(http2_parse_priority_update_frame)) {
+    EXPECT_CALL(visitor, OnPriorityUpdate(3, "foo"));
+  } else {
+    EXPECT_CALL(visitor, OnUnknownFrame(0, _)).WillOnce(testing::Return(true));
+  }
+
+  deframer_.ProcessInput(kFrameData, sizeof(kFrameData));
+  EXPECT_FALSE(deframer_.HasError());
+}
+
+TEST_P(SpdyFramerTest, ReadPriorityUpdateFrameWithEmptyPriorityFieldValue) {
+  const char kFrameData[] = {
+      0x00, 0x00, 0x04,        // payload length
+      0x10,                    // frame type PRIORITY_UPDATE
+      0x00,                    // flags
+      0x00, 0x00, 0x00, 0x00,  // stream ID, must be 0
+      0x00, 0x00, 0x00, 0x03   // prioritized stream ID, must not be zero
+  };
+
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+  deframer_.set_visitor(&visitor);
+
+  if (GetHttp2RestartFlag(http2_parse_priority_update_frame)) {
+    EXPECT_CALL(visitor, OnPriorityUpdate(3, ""));
+  } else {
+    EXPECT_CALL(visitor, OnUnknownFrame(0, _)).WillOnce(testing::Return(true));
+  }
+
+  deframer_.ProcessInput(kFrameData, sizeof(kFrameData));
+  EXPECT_FALSE(deframer_.HasError());
+}
+
+TEST_P(SpdyFramerTest, PriorityUpdateFrameWithEmptyPayload) {
+  const char kFrameData[] = {
+      0x00, 0x00, 0x00,        // payload length
+      0x10,                    // frame type PRIORITY_UPDATE
+      0x00,                    // flags
+      0x00, 0x00, 0x00, 0x00,  // stream ID, must be 0
+  };
+
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+  deframer_.set_visitor(&visitor);
+
+  if (GetHttp2RestartFlag(http2_parse_priority_update_frame)) {
+    EXPECT_CALL(
+        visitor,
+        OnError(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE, _));
+    deframer_.ProcessInput(kFrameData, sizeof(kFrameData));
+    EXPECT_TRUE(deframer_.HasError());
+  } else {
+    EXPECT_CALL(visitor, OnUnknownFrame(0, _)).WillOnce(testing::Return(true));
+    deframer_.ProcessInput(kFrameData, sizeof(kFrameData));
+    EXPECT_FALSE(deframer_.HasError());
+  }
+}
+
+TEST_P(SpdyFramerTest, PriorityUpdateFrameWithShortPayload) {
+  const char kFrameData[] = {
+      0x00, 0x00, 0x02,        // payload length
+      0x10,                    // frame type PRIORITY_UPDATE
+      0x00,                    // flags
+      0x00, 0x00, 0x00, 0x00,  // stream ID, must be 0
+      0x00, 0x01  // payload not long enough to hold 32 bits of prioritized
+                  // stream ID
+  };
+
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+  deframer_.set_visitor(&visitor);
+
+  if (GetHttp2RestartFlag(http2_parse_priority_update_frame)) {
+    EXPECT_CALL(
+        visitor,
+        OnError(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE, _));
+    deframer_.ProcessInput(kFrameData, sizeof(kFrameData));
+    EXPECT_TRUE(deframer_.HasError());
+  } else {
+    EXPECT_CALL(visitor, OnUnknownFrame(0, _)).WillOnce(testing::Return(true));
+    deframer_.ProcessInput(kFrameData, sizeof(kFrameData));
+    EXPECT_FALSE(deframer_.HasError());
+  }
+}
+
+TEST_P(SpdyFramerTest, PriorityUpdateFrameOnIncorrectStream) {
+  const char kFrameData[] = {
+      0x00, 0x00, 0x04,        // payload length
+      0x10,                    // frame type PRIORITY_UPDATE
+      0x00,                    // flags
+      0x00, 0x00, 0x00, 0x01,  // invalid stream ID, must be 0
+      0x00, 0x00, 0x00, 0x01,  // prioritized stream ID, must not be zero
+  };
+
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+  deframer_.set_visitor(&visitor);
+
+  if (GetHttp2RestartFlag(http2_parse_priority_update_frame)) {
+    EXPECT_CALL(visitor,
+                OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, _));
+    deframer_.ProcessInput(kFrameData, sizeof(kFrameData));
+    EXPECT_TRUE(deframer_.HasError());
+  } else {
+    EXPECT_CALL(visitor, OnUnknownFrame(1, _)).WillOnce(testing::Return(true));
+    deframer_.ProcessInput(kFrameData, sizeof(kFrameData));
+    EXPECT_FALSE(deframer_.HasError());
+  }
+}
+
+TEST_P(SpdyFramerTest, PriorityUpdateFramePrioritizingIncorrectStream) {
+  const char kFrameData[] = {
+      0x00, 0x00, 0x04,        // payload length
+      0x10,                    // frame type PRIORITY_UPDATE
+      0x00,                    // flags
+      0x00, 0x00, 0x00, 0x00,  // stream ID, must be 0
+      0x00, 0x00, 0x00, 0x00,  // prioritized stream ID, must not be zero
+  };
+
+  testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+  deframer_.set_visitor(&visitor);
+
+  if (GetHttp2RestartFlag(http2_parse_priority_update_frame)) {
+    EXPECT_CALL(visitor,
+                OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, _));
+    deframer_.ProcessInput(kFrameData, sizeof(kFrameData));
+    EXPECT_TRUE(deframer_.HasError());
+  } else {
+    EXPECT_CALL(visitor, OnUnknownFrame(0, _)).WillOnce(testing::Return(true));
+    deframer_.ProcessInput(kFrameData, sizeof(kFrameData));
+    EXPECT_FALSE(deframer_.HasError());
+  }
+}
+
 // Tests handling of PRIORITY frames.
 TEST_P(SpdyFramerTest, ReadPriority) {
   SpdyPriorityIR priority(/* stream_id = */ 3,