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,