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).