Project import generated by Copybara.
PiperOrigin-RevId: 224614037
Change-Id: I14e53449d4aeccb328f86828c76b5f09dea0d4b8
diff --git a/http2/decoder/payload_decoders/altsvc_payload_decoder.cc b/http2/decoder/payload_decoders/altsvc_payload_decoder.cc
new file mode 100644
index 0000000..4e4d860
--- /dev/null
+++ b/http2/decoder/payload_decoders/altsvc_payload_decoder.cc
@@ -0,0 +1,148 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+
+namespace http2 {
+
+std::ostream& operator<<(std::ostream& out,
+ AltSvcPayloadDecoder::PayloadState v) {
+ switch (v) {
+ case AltSvcPayloadDecoder::PayloadState::kStartDecodingStruct:
+ return out << "kStartDecodingStruct";
+ case AltSvcPayloadDecoder::PayloadState::kMaybeDecodedStruct:
+ return out << "kMaybeDecodedStruct";
+ case AltSvcPayloadDecoder::PayloadState::kDecodingStrings:
+ return out << "kDecodingStrings";
+ case AltSvcPayloadDecoder::PayloadState::kResumeDecodingStruct:
+ return out << "kResumeDecodingStruct";
+ }
+ // 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 AltSvcPayloadDecoder::PayloadState: " << unknown;
+ return out << "AltSvcPayloadDecoder::PayloadState(" << unknown << ")";
+}
+
+DecodeStatus AltSvcPayloadDecoder::StartDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "AltSvcPayloadDecoder::StartDecodingPayload: "
+ << state->frame_header();
+ DCHECK_EQ(Http2FrameType::ALTSVC, 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::kStartDecodingStruct;
+
+ return ResumeDecodingPayload(state, db);
+}
+
+DecodeStatus AltSvcPayloadDecoder::ResumeDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ const Http2FrameHeader& frame_header = state->frame_header();
+ DVLOG(2) << "AltSvcPayloadDecoder::ResumeDecodingPayload: " << frame_header;
+ DCHECK_EQ(Http2FrameType::ALTSVC, frame_header.type);
+ DCHECK_LE(state->remaining_payload(), frame_header.payload_length);
+ DCHECK_LE(db->Remaining(), state->remaining_payload());
+ DCHECK_NE(PayloadState::kMaybeDecodedStruct, payload_state_);
+ // |status| has to be initialized to some value to avoid compiler error in
+ // case PayloadState::kMaybeDecodedStruct below, but value does not matter,
+ // see DCHECK_NE above.
+ DecodeStatus status = DecodeStatus::kDecodeError;
+ while (true) {
+ DVLOG(2) << "AltSvcPayloadDecoder::ResumeDecodingPayload payload_state_="
+ << payload_state_;
+ switch (payload_state_) {
+ case PayloadState::kStartDecodingStruct:
+ status = state->StartDecodingStructureInPayload(&altsvc_fields_, db);
+ HTTP2_FALLTHROUGH;
+
+ case PayloadState::kMaybeDecodedStruct:
+ if (status == DecodeStatus::kDecodeDone &&
+ altsvc_fields_.origin_length <= state->remaining_payload()) {
+ size_t origin_length = altsvc_fields_.origin_length;
+ size_t value_length = state->remaining_payload() - origin_length;
+ state->listener()->OnAltSvcStart(frame_header, origin_length,
+ value_length);
+ } else if (status != DecodeStatus::kDecodeDone) {
+ DCHECK(state->remaining_payload() > 0 ||
+ status == DecodeStatus::kDecodeError)
+ << "\nremaining_payload: " << state->remaining_payload()
+ << "\nstatus: " << status << "\nheader: " << frame_header;
+ // Assume in progress.
+ payload_state_ = PayloadState::kResumeDecodingStruct;
+ return status;
+ } else {
+ // The origin's length is longer than the remaining payload.
+ DCHECK_GT(altsvc_fields_.origin_length, state->remaining_payload());
+ return state->ReportFrameSizeError();
+ }
+ HTTP2_FALLTHROUGH;
+
+ case PayloadState::kDecodingStrings:
+ return DecodeStrings(state, db);
+
+ case PayloadState::kResumeDecodingStruct:
+ status = state->ResumeDecodingStructureInPayload(&altsvc_fields_, db);
+ payload_state_ = PayloadState::kMaybeDecodedStruct;
+ continue;
+ }
+ HTTP2_BUG << "PayloadState: " << payload_state_;
+ }
+}
+
+DecodeStatus AltSvcPayloadDecoder::DecodeStrings(FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "AltSvcPayloadDecoder::DecodeStrings remaining_payload="
+ << state->remaining_payload()
+ << ", db->Remaining=" << db->Remaining();
+ // Note that we don't explicitly keep track of exactly how far through the
+ // origin; instead we compute it from how much is left of the original
+ // payload length and the decoded total length of the origin.
+ size_t origin_length = altsvc_fields_.origin_length;
+ size_t value_length = state->frame_header().payload_length - origin_length -
+ Http2AltSvcFields::EncodedSize();
+ if (state->remaining_payload() > value_length) {
+ size_t remaining_origin_length = state->remaining_payload() - value_length;
+ size_t avail = db->MinLengthRemaining(remaining_origin_length);
+ state->listener()->OnAltSvcOriginData(db->cursor(), avail);
+ db->AdvanceCursor(avail);
+ state->ConsumePayload(avail);
+ if (remaining_origin_length > avail) {
+ payload_state_ = PayloadState::kDecodingStrings;
+ return DecodeStatus::kDecodeInProgress;
+ }
+ }
+ // All that is left is the value string.
+ DCHECK_LE(state->remaining_payload(), value_length);
+ DCHECK_LE(db->Remaining(), state->remaining_payload());
+ if (db->HasData()) {
+ size_t avail = db->Remaining();
+ state->listener()->OnAltSvcValueData(db->cursor(), avail);
+ db->AdvanceCursor(avail);
+ state->ConsumePayload(avail);
+ }
+ if (state->remaining_payload() == 0) {
+ state->listener()->OnAltSvcEnd();
+ return DecodeStatus::kDecodeDone;
+ }
+ payload_state_ = PayloadState::kDecodingStrings;
+ return DecodeStatus::kDecodeInProgress;
+}
+
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/altsvc_payload_decoder.h b/http2/decoder/payload_decoders/altsvc_payload_decoder.h
new file mode 100644
index 0000000..e3523f9
--- /dev/null
+++ b/http2/decoder/payload_decoders/altsvc_payload_decoder.h
@@ -0,0 +1,64 @@
+// Copyright 2016 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_ALTSVC_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_ALTSVC_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a ALTSVC frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class AltSvcPayloadDecoderPeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE AltSvcPayloadDecoder {
+ public:
+ // States during decoding of a ALTSVC frame.
+ enum class PayloadState {
+ // Start decoding the fixed size structure at the start of an ALTSVC
+ // frame (Http2AltSvcFields).
+ kStartDecodingStruct,
+
+ // Handle the DecodeStatus returned from starting or resuming the
+ // decoding of Http2AltSvcFields. If complete, calls OnAltSvcStart.
+ kMaybeDecodedStruct,
+
+ // Reports the value of the strings (origin and value) of an ALTSVC frame
+ // to the listener.
+ kDecodingStrings,
+
+ // The initial decode buffer wasn't large enough for the Http2AltSvcFields,
+ // so this state resumes the decoding when ResumeDecodingPayload is called
+ // later with a new DecodeBuffer.
+ kResumeDecodingStruct,
+ };
+
+ // Starts the decoding of a ALTSVC 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 ALTSVC frame's payload that has been split across
+ // decode buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+
+ private:
+ friend class test::AltSvcPayloadDecoderPeer;
+
+ // Implements state kDecodingStrings.
+ DecodeStatus DecodeStrings(FrameDecoderState* state, DecodeBuffer* db);
+
+ Http2AltSvcFields altsvc_fields_;
+ PayloadState payload_state_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_ALTSVC_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/altsvc_payload_decoder_test.cc b/http2/decoder/payload_decoders/altsvc_payload_decoder_test.cc
new file mode 100644
index 0000000..bf928d3
--- /dev/null
+++ b/http2/decoder/payload_decoders/altsvc_payload_decoder_test.cc
@@ -0,0 +1,121 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+// Provides friend access to an instance of the payload decoder, and also
+// provides info to aid in testing.
+class AltSvcPayloadDecoderPeer {
+ public:
+ static constexpr Http2FrameType FrameType() { return Http2FrameType::ALTSVC; }
+
+ // 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 OnAltSvcStart(const Http2FrameHeader& header,
+ size_t origin_length,
+ size_t value_length) override {
+ VLOG(1) << "OnAltSvcStart header: " << header
+ << "; origin_length=" << origin_length
+ << "; value_length=" << value_length;
+ StartFrame(header)->OnAltSvcStart(header, origin_length, value_length);
+ }
+
+ void OnAltSvcOriginData(const char* data, size_t len) override {
+ VLOG(1) << "OnAltSvcOriginData: len=" << len;
+ CurrentFrame()->OnAltSvcOriginData(data, len);
+ }
+
+ void OnAltSvcValueData(const char* data, size_t len) override {
+ VLOG(1) << "OnAltSvcValueData: len=" << len;
+ CurrentFrame()->OnAltSvcValueData(data, len);
+ }
+
+ void OnAltSvcEnd() override {
+ VLOG(1) << "OnAltSvcEnd";
+ EndFrame()->OnAltSvcEnd();
+ }
+
+ void OnFrameSizeError(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnFrameSizeError: " << header;
+ FrameError(header)->OnFrameSizeError(header);
+ }
+};
+
+class AltSvcPayloadDecoderTest
+ : public AbstractPayloadDecoderTest<AltSvcPayloadDecoder,
+ AltSvcPayloadDecoderPeer,
+ Listener> {};
+
+// Confirm we get an error if the payload is not long enough to hold
+// Http2AltSvcFields and the indicated length of origin.
+TEST_F(AltSvcPayloadDecoderTest, Truncated) {
+ Http2FrameBuilder fb;
+ fb.Append(Http2AltSvcFields{0xffff}); // The longest possible origin length.
+ fb.Append("Too little origin!");
+ EXPECT_TRUE(
+ VerifyDetectsFrameSizeError(0, fb.buffer(), /*approve_size*/ nullptr));
+}
+
+class AltSvcPayloadLengthTests : public AltSvcPayloadDecoderTest,
+ public ::testing::WithParamInterface<
+ ::testing::tuple<uint16_t, uint32_t>> {
+ protected:
+ AltSvcPayloadLengthTests()
+ : origin_length_(::testing::get<0>(GetParam())),
+ value_length_(::testing::get<1>(GetParam())) {
+ VLOG(1) << "################ origin_length_=" << origin_length_
+ << " value_length_=" << value_length_ << " ################";
+ }
+
+ const uint16_t origin_length_;
+ const uint32_t value_length_;
+};
+
+INSTANTIATE_TEST_CASE_P(VariousOriginAndValueLengths,
+ AltSvcPayloadLengthTests,
+ ::testing::Combine(::testing::Values(0, 1, 3, 65535),
+ ::testing::Values(0, 1, 3, 65537)));
+
+TEST_P(AltSvcPayloadLengthTests, ValidOriginAndValueLength) {
+ Http2String origin = Random().RandString(origin_length_);
+ Http2String value = Random().RandString(value_length_);
+ Http2FrameBuilder fb;
+ fb.Append(Http2AltSvcFields{origin_length_});
+ fb.Append(origin);
+ fb.Append(value);
+ Http2FrameHeader header(fb.size(), Http2FrameType::ALTSVC, RandFlags(),
+ RandStreamId());
+ set_frame_header(header);
+ FrameParts expected(header);
+ expected.SetAltSvcExpected(origin, value);
+ ASSERT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/continuation_payload_decoder.cc b/http2/decoder/payload_decoders/continuation_payload_decoder.cc
new file mode 100644
index 0000000..aa9c817
--- /dev/null
+++ b/http2/decoder/payload_decoders/continuation_payload_decoder.cc
@@ -0,0 +1,58 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+namespace http2 {
+
+DecodeStatus ContinuationPayloadDecoder::StartDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ const Http2FrameHeader& frame_header = state->frame_header();
+ const uint32_t total_length = frame_header.payload_length;
+
+ DVLOG(2) << "ContinuationPayloadDecoder::StartDecodingPayload: "
+ << frame_header;
+ DCHECK_EQ(Http2FrameType::CONTINUATION, frame_header.type);
+ DCHECK_LE(db->Remaining(), total_length);
+ DCHECK_EQ(0, frame_header.flags & ~(Http2FrameFlag::END_HEADERS));
+
+ state->InitializeRemainders();
+ state->listener()->OnContinuationStart(frame_header);
+ return ResumeDecodingPayload(state, db);
+}
+
+DecodeStatus ContinuationPayloadDecoder::ResumeDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "ContinuationPayloadDecoder::ResumeDecodingPayload"
+ << " remaining_payload=" << state->remaining_payload()
+ << " db->Remaining=" << db->Remaining();
+ DCHECK_EQ(Http2FrameType::CONTINUATION, state->frame_header().type);
+ DCHECK_LE(state->remaining_payload(), state->frame_header().payload_length);
+ DCHECK_LE(db->Remaining(), state->remaining_payload());
+
+ size_t avail = db->Remaining();
+ DCHECK_LE(avail, state->remaining_payload());
+ if (avail > 0) {
+ state->listener()->OnHpackFragment(db->cursor(), avail);
+ db->AdvanceCursor(avail);
+ state->ConsumePayload(avail);
+ }
+ if (state->remaining_payload() == 0) {
+ state->listener()->OnContinuationEnd();
+ return DecodeStatus::kDecodeDone;
+ }
+ return DecodeStatus::kDecodeInProgress;
+}
+
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/continuation_payload_decoder.h b/http2/decoder/payload_decoders/continuation_payload_decoder.h
new file mode 100644
index 0000000..63b16ae
--- /dev/null
+++ b/http2/decoder/payload_decoders/continuation_payload_decoder.h
@@ -0,0 +1,31 @@
+// Copyright 2016 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_CONTINUATION_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_CONTINUATION_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a CONTINUATION frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE ContinuationPayloadDecoder {
+ public:
+ // Starts the decoding of a CONTINUATION 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 CONTINUATION frame's payload that has been split across
+ // decode buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_CONTINUATION_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/continuation_payload_decoder_test.cc b/http2/decoder/payload_decoders/continuation_payload_decoder_test.cc
new file mode 100644
index 0000000..63888d3
--- /dev/null
+++ b/http2/decoder/payload_decoders/continuation_payload_decoder_test.cc
@@ -0,0 +1,85 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder.h"
+
+#include <stddef.h>
+
+#include <type_traits>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+// Provides friend access to an instance of the payload decoder, and also
+// provides info to aid in testing.
+class ContinuationPayloadDecoderPeer {
+ public:
+ static constexpr Http2FrameType FrameType() {
+ return Http2FrameType::CONTINUATION;
+ }
+
+ // 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 OnContinuationStart(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnContinuationStart: " << header;
+ StartFrame(header)->OnContinuationStart(header);
+ }
+
+ void OnHpackFragment(const char* data, size_t len) override {
+ VLOG(1) << "OnHpackFragment: len=" << len;
+ CurrentFrame()->OnHpackFragment(data, len);
+ }
+
+ void OnContinuationEnd() override {
+ VLOG(1) << "OnContinuationEnd";
+ EndFrame()->OnContinuationEnd();
+ }
+};
+
+class ContinuationPayloadDecoderTest
+ : public AbstractPayloadDecoderTest<ContinuationPayloadDecoder,
+ ContinuationPayloadDecoderPeer,
+ Listener>,
+ public ::testing::WithParamInterface<uint32_t> {
+ protected:
+ ContinuationPayloadDecoderTest() : length_(GetParam()) {
+ VLOG(1) << "################ length_=" << length_ << " ################";
+ }
+
+ const uint32_t length_;
+};
+
+INSTANTIATE_TEST_CASE_P(VariousLengths,
+ ContinuationPayloadDecoderTest,
+ ::testing::Values(0, 1, 2, 3, 4, 5, 6));
+
+TEST_P(ContinuationPayloadDecoderTest, ValidLength) {
+ Http2String hpack_payload = Random().RandString(length_);
+ Http2FrameHeader frame_header(length_, Http2FrameType::CONTINUATION,
+ RandFlags(), RandStreamId());
+ set_frame_header(frame_header);
+ FrameParts expected(frame_header, hpack_payload);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(hpack_payload, expected));
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/data_payload_decoder.cc b/http2/decoder/payload_decoders/data_payload_decoder.cc
new file mode 100644
index 0000000..b6b8041
--- /dev/null
+++ b/http2/decoder/payload_decoders/data_payload_decoder.cc
@@ -0,0 +1,127 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+
+namespace http2 {
+
+std::ostream& operator<<(std::ostream& out,
+ DataPayloadDecoder::PayloadState v) {
+ switch (v) {
+ case DataPayloadDecoder::PayloadState::kReadPadLength:
+ return out << "kReadPadLength";
+ case DataPayloadDecoder::PayloadState::kReadPayload:
+ return out << "kReadPayload";
+ case DataPayloadDecoder::PayloadState::kSkipPadding:
+ return out << "kSkipPadding";
+ }
+ // 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 DataPayloadDecoder::PayloadState: " << unknown;
+ return out << "DataPayloadDecoder::PayloadState(" << unknown << ")";
+}
+
+DecodeStatus DataPayloadDecoder::StartDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db) {
+ const Http2FrameHeader& frame_header = state->frame_header();
+ const uint32_t total_length = frame_header.payload_length;
+
+ DVLOG(2) << "DataPayloadDecoder::StartDecodingPayload: " << frame_header;
+ DCHECK_EQ(Http2FrameType::DATA, frame_header.type);
+ DCHECK_LE(db->Remaining(), total_length);
+ DCHECK_EQ(0, frame_header.flags &
+ ~(Http2FrameFlag::END_STREAM | Http2FrameFlag::PADDED));
+
+ // Special case for the hoped for common case: unpadded and fits fully into
+ // the decode buffer. TO BE SEEN if that is true. It certainly requires that
+ // the transport buffers be large (e.g. >> 16KB typically).
+ // TODO(jamessynge) Add counters.
+ DVLOG(2) << "StartDecodingPayload total_length=" << total_length;
+ if (!frame_header.IsPadded()) {
+ DVLOG(2) << "StartDecodingPayload !IsPadded";
+ if (db->Remaining() == total_length) {
+ DVLOG(2) << "StartDecodingPayload all present";
+ // Note that we don't cache the listener field so that the callee can
+ // replace it if the frame is bad.
+ // If this case is common enough, consider combining the 3 callbacks
+ // into one.
+ state->listener()->OnDataStart(frame_header);
+ if (total_length > 0) {
+ state->listener()->OnDataPayload(db->cursor(), total_length);
+ db->AdvanceCursor(total_length);
+ }
+ state->listener()->OnDataEnd();
+ return DecodeStatus::kDecodeDone;
+ }
+ payload_state_ = PayloadState::kReadPayload;
+ } else {
+ payload_state_ = PayloadState::kReadPadLength;
+ }
+ state->InitializeRemainders();
+ state->listener()->OnDataStart(frame_header);
+ return ResumeDecodingPayload(state, db);
+}
+
+DecodeStatus DataPayloadDecoder::ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "DataPayloadDecoder::ResumeDecodingPayload payload_state_="
+ << payload_state_;
+ const Http2FrameHeader& frame_header = state->frame_header();
+ DCHECK_EQ(Http2FrameType::DATA, frame_header.type);
+ DCHECK_LE(state->remaining_payload_and_padding(),
+ frame_header.payload_length);
+ DCHECK_LE(db->Remaining(), state->remaining_payload_and_padding());
+ DecodeStatus status;
+ size_t avail;
+ switch (payload_state_) {
+ case PayloadState::kReadPadLength:
+ // ReadPadLength handles the OnPadLength callback, and updating the
+ // remaining_payload and remaining_padding fields. If the amount of
+ // padding is too large to fit in the frame's payload, ReadPadLength
+ // instead calls OnPaddingTooLong and returns kDecodeError.
+ status = state->ReadPadLength(db, /*report_pad_length*/ true);
+ if (status != DecodeStatus::kDecodeDone) {
+ return status;
+ }
+ HTTP2_FALLTHROUGH;
+
+ case PayloadState::kReadPayload:
+ avail = state->AvailablePayload(db);
+ if (avail > 0) {
+ state->listener()->OnDataPayload(db->cursor(), avail);
+ db->AdvanceCursor(avail);
+ state->ConsumePayload(avail);
+ }
+ if (state->remaining_payload() > 0) {
+ payload_state_ = PayloadState::kReadPayload;
+ return DecodeStatus::kDecodeInProgress;
+ }
+ HTTP2_FALLTHROUGH;
+
+ case PayloadState::kSkipPadding:
+ // SkipPadding handles the OnPadding callback.
+ if (state->SkipPadding(db)) {
+ state->listener()->OnDataEnd();
+ return DecodeStatus::kDecodeDone;
+ }
+ payload_state_ = PayloadState::kSkipPadding;
+ return DecodeStatus::kDecodeInProgress;
+ }
+ HTTP2_BUG << "PayloadState: " << payload_state_;
+ return DecodeStatus::kDecodeError;
+}
+
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/data_payload_decoder.h b/http2/decoder/payload_decoders/data_payload_decoder.h
new file mode 100644
index 0000000..1e083a9
--- /dev/null
+++ b/http2/decoder/payload_decoders/data_payload_decoder.h
@@ -0,0 +1,54 @@
+// Copyright 2016 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_DATA_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_DATA_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a DATA frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class DataPayloadDecoderPeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE DataPayloadDecoder {
+ public:
+ // States during decoding of a DATA frame.
+ enum class PayloadState {
+ // The frame is padded and we need to read the PAD_LENGTH field (1 byte),
+ // and then call OnPadLength
+ kReadPadLength,
+
+ // Report the non-padding portion of the payload to the listener's
+ // OnDataPayload method.
+ kReadPayload,
+
+ // The decoder has finished with the non-padding portion of the payload,
+ // and is now ready to skip the trailing padding, if the frame has any.
+ kSkipPadding,
+ };
+
+ // Starts decoding a DATA 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 DATA frame's payload that has been split across
+ // decode buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+
+ private:
+ friend class test::DataPayloadDecoderPeer;
+
+ PayloadState payload_state_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_DATA_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/data_payload_decoder_test.cc b/http2/decoder/payload_decoders/data_payload_decoder_test.cc
new file mode 100644
index 0000000..e5253b9
--- /dev/null
+++ b/http2/decoder/payload_decoders/data_payload_decoder_test.cc
@@ -0,0 +1,113 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionResult;
+
+namespace http2 {
+namespace test {
+
+// Provides friend access to an instance of the payload decoder, and also
+// provides info to aid in testing.
+class DataPayloadDecoderPeer {
+ public:
+ static constexpr Http2FrameType FrameType() { return Http2FrameType::DATA; }
+
+ // 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 Http2FrameFlag::PADDED;
+ }
+};
+
+namespace {
+
+struct Listener : public FramePartsCollector {
+ void OnDataStart(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnDataStart: " << header;
+ StartFrame(header)->OnDataStart(header);
+ }
+
+ void OnDataPayload(const char* data, size_t len) override {
+ VLOG(1) << "OnDataPayload: len=" << len;
+ CurrentFrame()->OnDataPayload(data, len);
+ }
+
+ void OnDataEnd() override {
+ VLOG(1) << "OnDataEnd";
+ EndFrame()->OnDataEnd();
+ }
+
+ void OnPadLength(size_t pad_length) override {
+ VLOG(1) << "OnPadLength: " << pad_length;
+ CurrentFrame()->OnPadLength(pad_length);
+ }
+
+ void OnPadding(const char* padding, size_t skipped_length) override {
+ VLOG(1) << "OnPadding: " << skipped_length;
+ CurrentFrame()->OnPadding(padding, skipped_length);
+ }
+
+ void OnPaddingTooLong(const Http2FrameHeader& header,
+ size_t missing_length) override {
+ VLOG(1) << "OnPaddingTooLong: " << header
+ << " missing_length: " << missing_length;
+ EndFrame()->OnPaddingTooLong(header, missing_length);
+ }
+};
+
+class DataPayloadDecoderTest
+ : public AbstractPaddablePayloadDecoderTest<DataPayloadDecoder,
+ DataPayloadDecoderPeer,
+ Listener> {
+ protected:
+ AssertionResult CreateAndDecodeDataOfSize(size_t data_size) {
+ Reset();
+ uint8_t flags = RandFlags();
+
+ Http2String data_payload = Random().RandString(data_size);
+ frame_builder_.Append(data_payload);
+ MaybeAppendTrailingPadding();
+
+ Http2FrameHeader frame_header(frame_builder_.size(), Http2FrameType::DATA,
+ flags, RandStreamId());
+ set_frame_header(frame_header);
+ ScrubFlagsOfHeader(&frame_header);
+ FrameParts expected(frame_header, data_payload, total_pad_length_);
+ VERIFY_AND_RETURN_SUCCESS(
+ DecodePayloadAndValidateSeveralWays(frame_builder_.buffer(), expected));
+ }
+};
+
+INSTANTIATE_TEST_CASE_P(VariousPadLengths,
+ DataPayloadDecoderTest,
+ ::testing::Values(0, 1, 2, 3, 4, 254, 255, 256));
+
+TEST_P(DataPayloadDecoderTest, VariousDataPayloadSizes) {
+ for (size_t data_size : {0, 1, 2, 3, 255, 256, 1024}) {
+ EXPECT_TRUE(CreateAndDecodeDataOfSize(data_size));
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/goaway_payload_decoder.cc b/http2/decoder/payload_decoders/goaway_payload_decoder.cc
new file mode 100644
index 0000000..cf7b673
--- /dev/null
+++ b/http2/decoder/payload_decoders/goaway_payload_decoder.cc
@@ -0,0 +1,121 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+
+namespace http2 {
+
+std::ostream& operator<<(std::ostream& out,
+ GoAwayPayloadDecoder::PayloadState v) {
+ switch (v) {
+ case GoAwayPayloadDecoder::PayloadState::kStartDecodingFixedFields:
+ return out << "kStartDecodingFixedFields";
+ case GoAwayPayloadDecoder::PayloadState::kHandleFixedFieldsStatus:
+ return out << "kHandleFixedFieldsStatus";
+ case GoAwayPayloadDecoder::PayloadState::kReadOpaqueData:
+ return out << "kReadOpaqueData";
+ case GoAwayPayloadDecoder::PayloadState::kResumeDecodingFixedFields:
+ return out << "kResumeDecodingFixedFields";
+ }
+ // 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 GoAwayPayloadDecoder::PayloadState: " << unknown;
+ return out << "GoAwayPayloadDecoder::PayloadState(" << unknown << ")";
+}
+
+DecodeStatus GoAwayPayloadDecoder::StartDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "GoAwayPayloadDecoder::StartDecodingPayload: "
+ << state->frame_header();
+ DCHECK_EQ(Http2FrameType::GOAWAY, 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 GoAwayPayloadDecoder::ResumeDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "GoAwayPayloadDecoder::ResumeDecodingPayload: remaining_payload="
+ << state->remaining_payload()
+ << ", db->Remaining=" << db->Remaining();
+
+ const Http2FrameHeader& frame_header = state->frame_header();
+ DCHECK_EQ(Http2FrameType::GOAWAY, 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) {
+ DVLOG(2) << "GoAwayPayloadDecoder::ResumeDecodingPayload payload_state_="
+ << payload_state_;
+ switch (payload_state_) {
+ case PayloadState::kStartDecodingFixedFields:
+ status = state->StartDecodingStructureInPayload(&goaway_fields_, db);
+ HTTP2_FALLTHROUGH;
+
+ case PayloadState::kHandleFixedFieldsStatus:
+ if (status == DecodeStatus::kDecodeDone) {
+ state->listener()->OnGoAwayStart(frame_header, goaway_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::kReadOpaqueData:
+ // The opaque data is all the remains to be decoded, so anything left
+ // in the decode buffer is opaque data.
+ avail = db->Remaining();
+ if (avail > 0) {
+ state->listener()->OnGoAwayOpaqueData(db->cursor(), avail);
+ db->AdvanceCursor(avail);
+ state->ConsumePayload(avail);
+ }
+ if (state->remaining_payload() > 0) {
+ payload_state_ = PayloadState::kReadOpaqueData;
+ return DecodeStatus::kDecodeInProgress;
+ }
+ state->listener()->OnGoAwayEnd();
+ return DecodeStatus::kDecodeDone;
+
+ case PayloadState::kResumeDecodingFixedFields:
+ status = state->ResumeDecodingStructureInPayload(&goaway_fields_, db);
+ payload_state_ = PayloadState::kHandleFixedFieldsStatus;
+ continue;
+ }
+ HTTP2_BUG << "PayloadState: " << payload_state_;
+ }
+}
+
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/goaway_payload_decoder.h b/http2/decoder/payload_decoders/goaway_payload_decoder.h
new file mode 100644
index 0000000..7a50873
--- /dev/null
+++ b/http2/decoder/payload_decoders/goaway_payload_decoder.h
@@ -0,0 +1,66 @@
+// Copyright 2016 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_GOAWAY_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_GOAWAY_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a GOAWAY frame.
+
+// TODO(jamessynge): Sweep through all payload decoders, changing the names of
+// the PayloadState enums so that they are really states, and not actions.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class GoAwayPayloadDecoderPeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE GoAwayPayloadDecoder {
+ public:
+ // States during decoding of a GOAWAY frame.
+ enum class PayloadState {
+ // At the start of the GOAWAY frame payload, ready to start decoding the
+ // fixed size fields into goaway_fields_.
+ kStartDecodingFixedFields,
+
+ // Handle the DecodeStatus returned from starting or resuming the
+ // decoding of Http2GoAwayFields into goaway_fields_. If complete,
+ // calls OnGoAwayStart.
+ kHandleFixedFieldsStatus,
+
+ // Report the Opaque Data portion of the payload to the listener's
+ // OnGoAwayOpaqueData method, and call OnGoAwayEnd when the end of the
+ // payload is reached.
+ kReadOpaqueData,
+
+ // The fixed size fields weren't all available when the decoder first
+ // tried to decode them (state kStartDecodingFixedFields); this state
+ // resumes the decoding when ResumeDecodingPayload is called later.
+ kResumeDecodingFixedFields,
+ };
+
+ // Starts the decoding of a GOAWAY 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 GOAWAY frame's payload that has been split across
+ // decode buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+
+ private:
+ friend class test::GoAwayPayloadDecoderPeer;
+
+ Http2GoAwayFields goaway_fields_;
+ PayloadState payload_state_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_GOAWAY_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/goaway_payload_decoder_test.cc b/http2/decoder/payload_decoders/goaway_payload_decoder_test.cc
new file mode 100644
index 0000000..1df5214
--- /dev/null
+++ b/http2/decoder/payload_decoders/goaway_payload_decoder_test.cc
@@ -0,0 +1,107 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+class GoAwayPayloadDecoderPeer {
+ public:
+ static constexpr Http2FrameType FrameType() { return Http2FrameType::GOAWAY; }
+
+ // 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 OnGoAwayStart(const Http2FrameHeader& header,
+ const Http2GoAwayFields& goaway) override {
+ VLOG(1) << "OnGoAwayStart header: " << header << "; goaway: " << goaway;
+ StartFrame(header)->OnGoAwayStart(header, goaway);
+ }
+
+ void OnGoAwayOpaqueData(const char* data, size_t len) override {
+ VLOG(1) << "OnGoAwayOpaqueData: len=" << len;
+ CurrentFrame()->OnGoAwayOpaqueData(data, len);
+ }
+
+ void OnGoAwayEnd() override {
+ VLOG(1) << "OnGoAwayEnd";
+ EndFrame()->OnGoAwayEnd();
+ }
+
+ void OnFrameSizeError(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnFrameSizeError: " << header;
+ FrameError(header)->OnFrameSizeError(header);
+ }
+};
+
+class GoAwayPayloadDecoderTest
+ : public AbstractPayloadDecoderTest<GoAwayPayloadDecoder,
+ GoAwayPayloadDecoderPeer,
+ Listener> {};
+
+// Confirm we get an error if the payload is not long enough to hold
+// Http2GoAwayFields.
+TEST_F(GoAwayPayloadDecoderTest, Truncated) {
+ auto approve_size = [](size_t size) {
+ return size != Http2GoAwayFields::EncodedSize();
+ };
+ Http2FrameBuilder fb;
+ fb.Append(Http2GoAwayFields(123, Http2ErrorCode::ENHANCE_YOUR_CALM));
+ EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size));
+}
+
+class GoAwayOpaqueDataLengthTests
+ : public GoAwayPayloadDecoderTest,
+ public ::testing::WithParamInterface<uint32_t> {
+ protected:
+ GoAwayOpaqueDataLengthTests() : length_(GetParam()) {
+ VLOG(1) << "################ length_=" << length_ << " ################";
+ }
+
+ const uint32_t length_;
+};
+
+INSTANTIATE_TEST_CASE_P(VariousLengths,
+ GoAwayOpaqueDataLengthTests,
+ ::testing::Values(0, 1, 2, 3, 4, 5, 6));
+
+TEST_P(GoAwayOpaqueDataLengthTests, ValidLength) {
+ Http2GoAwayFields goaway;
+ Randomize(&goaway, RandomPtr());
+ Http2String opaque_data = Random().RandString(length_);
+ Http2FrameBuilder fb;
+ fb.Append(goaway);
+ fb.Append(opaque_data);
+ Http2FrameHeader header(fb.size(), Http2FrameType::GOAWAY, RandFlags(),
+ RandStreamId());
+ set_frame_header(header);
+ FrameParts expected(header, opaque_data);
+ expected.SetOptGoaway(goaway);
+ ASSERT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/headers_payload_decoder.cc b/http2/decoder/payload_decoders/headers_payload_decoder.cc
new file mode 100644
index 0000000..ecaf0fa
--- /dev/null
+++ b/http2/decoder/payload_decoders/headers_payload_decoder.cc
@@ -0,0 +1,175 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+
+namespace http2 {
+
+std::ostream& operator<<(std::ostream& out,
+ HeadersPayloadDecoder::PayloadState v) {
+ switch (v) {
+ case HeadersPayloadDecoder::PayloadState::kReadPadLength:
+ return out << "kReadPadLength";
+ case HeadersPayloadDecoder::PayloadState::kStartDecodingPriorityFields:
+ return out << "kStartDecodingPriorityFields";
+ case HeadersPayloadDecoder::PayloadState::kResumeDecodingPriorityFields:
+ return out << "kResumeDecodingPriorityFields";
+ case HeadersPayloadDecoder::PayloadState::kReadPayload:
+ return out << "kReadPayload";
+ case HeadersPayloadDecoder::PayloadState::kSkipPadding:
+ return out << "kSkipPadding";
+ }
+ // 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 HeadersPayloadDecoder::PayloadState: " << unknown;
+ return out << "HeadersPayloadDecoder::PayloadState(" << unknown << ")";
+}
+
+DecodeStatus HeadersPayloadDecoder::StartDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ const Http2FrameHeader& frame_header = state->frame_header();
+ const uint32_t total_length = frame_header.payload_length;
+
+ DVLOG(2) << "HeadersPayloadDecoder::StartDecodingPayload: " << frame_header;
+
+ DCHECK_EQ(Http2FrameType::HEADERS, frame_header.type);
+ DCHECK_LE(db->Remaining(), total_length);
+ DCHECK_EQ(0, frame_header.flags &
+ ~(Http2FrameFlag::END_STREAM | Http2FrameFlag::END_HEADERS |
+ Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY));
+
+ // Special case for HEADERS frames that contain only the HPACK block
+ // (fragment or whole) and that fit fully into the decode buffer.
+ // Why? Unencoded browser GET requests are typically under 1K and HPACK
+ // commonly shrinks request headers by 80%, so we can expect this to
+ // be common.
+ // TODO(jamessynge) Add counters here and to Spdy for determining how
+ // common this situation is. A possible approach is to create a
+ // Http2FrameDecoderListener that counts the callbacks and then forwards
+ // them on to another listener, which makes it easy to add and remove
+ // counting on a connection or even frame basis.
+
+ // PADDED and PRIORITY both extra steps to decode, but if neither flag is
+ // set then we can decode faster.
+ const auto payload_flags = Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY;
+ if (!frame_header.HasAnyFlags(payload_flags)) {
+ DVLOG(2) << "StartDecodingPayload !IsPadded && !HasPriority";
+ if (db->Remaining() == total_length) {
+ DVLOG(2) << "StartDecodingPayload all present";
+ // Note that we don't cache the listener field so that the callee can
+ // replace it if the frame is bad.
+ // If this case is common enough, consider combining the 3 callbacks
+ // into one, especially if END_HEADERS is also set.
+ state->listener()->OnHeadersStart(frame_header);
+ if (total_length > 0) {
+ state->listener()->OnHpackFragment(db->cursor(), total_length);
+ db->AdvanceCursor(total_length);
+ }
+ state->listener()->OnHeadersEnd();
+ return DecodeStatus::kDecodeDone;
+ }
+ payload_state_ = PayloadState::kReadPayload;
+ } else if (frame_header.IsPadded()) {
+ payload_state_ = PayloadState::kReadPadLength;
+ } else {
+ DCHECK(frame_header.HasPriority()) << frame_header;
+ payload_state_ = PayloadState::kStartDecodingPriorityFields;
+ }
+ state->InitializeRemainders();
+ state->listener()->OnHeadersStart(frame_header);
+ return ResumeDecodingPayload(state, db);
+}
+
+DecodeStatus HeadersPayloadDecoder::ResumeDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "HeadersPayloadDecoder::ResumeDecodingPayload "
+ << "remaining_payload=" << state->remaining_payload()
+ << "; db->Remaining=" << db->Remaining();
+
+ const Http2FrameHeader& frame_header = state->frame_header();
+
+ DCHECK_EQ(Http2FrameType::HEADERS, frame_header.type);
+ DCHECK_LE(state->remaining_payload_and_padding(),
+ frame_header.payload_length);
+ DCHECK_LE(db->Remaining(), state->remaining_payload_and_padding());
+ DecodeStatus status;
+ size_t avail;
+ while (true) {
+ DVLOG(2) << "HeadersPayloadDecoder::ResumeDecodingPayload payload_state_="
+ << payload_state_;
+ switch (payload_state_) {
+ case PayloadState::kReadPadLength:
+ // ReadPadLength handles the OnPadLength callback, and updating the
+ // remaining_payload and remaining_padding fields. If the amount of
+ // padding is too large to fit in the frame's payload, ReadPadLength
+ // instead calls OnPaddingTooLong and returns kDecodeError.
+ status = state->ReadPadLength(db, /*report_pad_length*/ true);
+ if (status != DecodeStatus::kDecodeDone) {
+ return status;
+ }
+ if (!frame_header.HasPriority()) {
+ payload_state_ = PayloadState::kReadPayload;
+ continue;
+ }
+ HTTP2_FALLTHROUGH;
+
+ case PayloadState::kStartDecodingPriorityFields:
+ status = state->StartDecodingStructureInPayload(&priority_fields_, db);
+ if (status != DecodeStatus::kDecodeDone) {
+ payload_state_ = PayloadState::kResumeDecodingPriorityFields;
+ return status;
+ }
+ state->listener()->OnHeadersPriority(priority_fields_);
+ HTTP2_FALLTHROUGH;
+
+ case PayloadState::kReadPayload:
+ avail = state->AvailablePayload(db);
+ if (avail > 0) {
+ state->listener()->OnHpackFragment(db->cursor(), avail);
+ db->AdvanceCursor(avail);
+ state->ConsumePayload(avail);
+ }
+ if (state->remaining_payload() > 0) {
+ payload_state_ = PayloadState::kReadPayload;
+ return DecodeStatus::kDecodeInProgress;
+ }
+ HTTP2_FALLTHROUGH;
+
+ case PayloadState::kSkipPadding:
+ // SkipPadding handles the OnPadding callback.
+ if (state->SkipPadding(db)) {
+ state->listener()->OnHeadersEnd();
+ return DecodeStatus::kDecodeDone;
+ }
+ payload_state_ = PayloadState::kSkipPadding;
+ return DecodeStatus::kDecodeInProgress;
+
+ case PayloadState::kResumeDecodingPriorityFields:
+ status = state->ResumeDecodingStructureInPayload(&priority_fields_, db);
+ if (status != DecodeStatus::kDecodeDone) {
+ return status;
+ }
+ state->listener()->OnHeadersPriority(priority_fields_);
+ payload_state_ = PayloadState::kReadPayload;
+ continue;
+ }
+ HTTP2_BUG << "PayloadState: " << payload_state_;
+ }
+}
+
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/headers_payload_decoder.h b/http2/decoder/payload_decoders/headers_payload_decoder.h
new file mode 100644
index 0000000..d3cdfe5
--- /dev/null
+++ b/http2/decoder/payload_decoders/headers_payload_decoder.h
@@ -0,0 +1,67 @@
+// Copyright 2016 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_HEADERS_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_HEADERS_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a HEADERS frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class HeadersPayloadDecoderPeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE HeadersPayloadDecoder {
+ public:
+ // States during decoding of a HEADERS frame, unless the fast path kicks
+ // in, in which case the state machine will be bypassed.
+ enum class PayloadState {
+ // The PADDED flag is set, and we now need to read the Pad Length field
+ // (the first byte of the payload, after the common frame header).
+ kReadPadLength,
+
+ // The PRIORITY flag is set, and we now need to read the fixed size priority
+ // fields (E, Stream Dependency, Weight) into priority_fields_. Calls on
+ // OnHeadersPriority if completely decodes those fields.
+ kStartDecodingPriorityFields,
+
+ // The decoder passes the non-padding portion of the remaining payload
+ // (i.e. the HPACK block fragment) to the listener's OnHpackFragment method.
+ kReadPayload,
+
+ // The decoder has finished with the HPACK block fragment, and is now
+ // ready to skip the trailing padding, if the frame has any.
+ kSkipPadding,
+
+ // The fixed size fields weren't all available when the decoder first tried
+ // to decode them (state kStartDecodingPriorityFields); this state resumes
+ // the decoding when ResumeDecodingPayload is called later.
+ kResumeDecodingPriorityFields,
+ };
+
+ // Starts the decoding of a HEADERS 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 HEADERS frame's payload that has been split across
+ // decode buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+
+ private:
+ friend class test::HeadersPayloadDecoderPeer;
+
+ PayloadState payload_state_;
+ Http2PriorityFields priority_fields_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_HEADERS_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/headers_payload_decoder_test.cc b/http2/decoder/payload_decoders/headers_payload_decoder_test.cc
new file mode 100644
index 0000000..a6fcb65
--- /dev/null
+++ b/http2/decoder/payload_decoders/headers_payload_decoder_test.cc
@@ -0,0 +1,158 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+class HeadersPayloadDecoderPeer {
+ public:
+ static constexpr Http2FrameType FrameType() {
+ return Http2FrameType::HEADERS;
+ }
+
+ // 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 Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY;
+ }
+};
+
+namespace {
+
+// Listener handles all On* methods that are expected to be called. If any other
+// On* methods of Http2FrameDecoderListener is called then the test fails; this
+// is achieved by way of FailingHttp2FrameDecoderListener, the base class of
+// FramePartsCollector.
+// These On* methods make use of StartFrame, EndFrame, etc. of the base class
+// to create and access to FrameParts instance(s) that will record the details.
+// After decoding, the test validation code can access the FramePart instance(s)
+// via the public methods of FramePartsCollector.
+struct Listener : public FramePartsCollector {
+ void OnHeadersStart(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnHeadersStart: " << header;
+ StartFrame(header)->OnHeadersStart(header);
+ }
+
+ void OnHeadersPriority(const Http2PriorityFields& priority) override {
+ VLOG(1) << "OnHeadersPriority: " << priority;
+ CurrentFrame()->OnHeadersPriority(priority);
+ }
+
+ void OnHpackFragment(const char* data, size_t len) override {
+ VLOG(1) << "OnHpackFragment: len=" << len;
+ CurrentFrame()->OnHpackFragment(data, len);
+ }
+
+ void OnHeadersEnd() override {
+ VLOG(1) << "OnHeadersEnd";
+ EndFrame()->OnHeadersEnd();
+ }
+
+ void OnPadLength(size_t pad_length) override {
+ VLOG(1) << "OnPadLength: " << pad_length;
+ CurrentFrame()->OnPadLength(pad_length);
+ }
+
+ void OnPadding(const char* padding, size_t skipped_length) override {
+ VLOG(1) << "OnPadding: " << skipped_length;
+ CurrentFrame()->OnPadding(padding, skipped_length);
+ }
+
+ void OnPaddingTooLong(const Http2FrameHeader& header,
+ size_t missing_length) override {
+ VLOG(1) << "OnPaddingTooLong: " << header
+ << "; missing_length: " << missing_length;
+ FrameError(header)->OnPaddingTooLong(header, missing_length);
+ }
+
+ void OnFrameSizeError(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnFrameSizeError: " << header;
+ FrameError(header)->OnFrameSizeError(header);
+ }
+};
+
+class HeadersPayloadDecoderTest
+ : public AbstractPaddablePayloadDecoderTest<HeadersPayloadDecoder,
+ HeadersPayloadDecoderPeer,
+ Listener> {};
+
+INSTANTIATE_TEST_CASE_P(VariousPadLengths,
+ HeadersPayloadDecoderTest,
+ ::testing::Values(0, 1, 2, 3, 4, 254, 255, 256));
+
+// Decode various sizes of (fake) HPACK payload, both with and without the
+// PRIORITY flag set.
+TEST_P(HeadersPayloadDecoderTest, VariousHpackPayloadSizes) {
+ for (size_t hpack_size : {0, 1, 2, 3, 255, 256, 1024}) {
+ LOG(INFO) << "########### hpack_size = " << hpack_size << " ###########";
+ Http2PriorityFields priority(RandStreamId(), 1 + Random().Rand8(),
+ Random().OneIn(2));
+
+ for (bool has_priority : {false, true}) {
+ Reset();
+ ASSERT_EQ(IsPadded() ? 1u : 0u, frame_builder_.size());
+ uint8_t flags = RandFlags();
+ if (has_priority) {
+ flags |= Http2FrameFlag::PRIORITY;
+ frame_builder_.Append(priority);
+ }
+
+ Http2String hpack_payload = Random().RandString(hpack_size);
+ frame_builder_.Append(hpack_payload);
+
+ MaybeAppendTrailingPadding();
+ Http2FrameHeader frame_header(frame_builder_.size(),
+ Http2FrameType::HEADERS, flags,
+ RandStreamId());
+ set_frame_header(frame_header);
+ ScrubFlagsOfHeader(&frame_header);
+ FrameParts expected(frame_header, hpack_payload, total_pad_length_);
+ if (has_priority) {
+ expected.SetOptPriority(priority);
+ }
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(frame_builder_.buffer(),
+ expected));
+ }
+ }
+}
+
+// Confirm we get an error if the PRIORITY flag is set but the payload is
+// not long enough, regardless of the amount of (valid) padding.
+TEST_P(HeadersPayloadDecoderTest, Truncated) {
+ auto approve_size = [](size_t size) {
+ return size != Http2PriorityFields::EncodedSize();
+ };
+ Http2FrameBuilder fb;
+ fb.Append(Http2PriorityFields(RandStreamId(), 1 + Random().Rand8(),
+ Random().OneIn(2)));
+ EXPECT_TRUE(VerifyDetectsMultipleFrameSizeErrors(
+ Http2FrameFlag::PRIORITY, fb.buffer(), approve_size, total_pad_length_));
+}
+
+// Confirm we get an error if the PADDED flag is set but the payload is not
+// long enough to hold even the Pad Length amount of padding.
+TEST_P(HeadersPayloadDecoderTest, PaddingTooLong) {
+ EXPECT_TRUE(VerifyDetectsPaddingTooLong());
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/payload_decoder_base_test_util.cc b/http2/decoder/payload_decoders/payload_decoder_base_test_util.cc
new file mode 100644
index 0000000..88e1b98
--- /dev/null
+++ b/http2/decoder/payload_decoders/payload_decoder_base_test_util.cc
@@ -0,0 +1,97 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+
+namespace http2 {
+namespace test {
+PayloadDecoderBaseTest::PayloadDecoderBaseTest() {
+ // If the test adds more data after the frame payload,
+ // stop as soon as the payload is decoded.
+ stop_decode_on_done_ = true;
+ frame_header_is_set_ = false;
+ Randomize(&frame_header_, RandomPtr());
+}
+
+DecodeStatus PayloadDecoderBaseTest::StartDecoding(DecodeBuffer* db) {
+ DVLOG(2) << "StartDecoding, db->Remaining=" << db->Remaining();
+ // Make sure sub-class has set frame_header_ so that we can inject it
+ // into the payload decoder below.
+ if (!frame_header_is_set_) {
+ ADD_FAILURE() << "frame_header_ is not set";
+ return DecodeStatus::kDecodeError;
+ }
+ // The contract with the payload decoders is that they won't receive a
+ // decode buffer that extends beyond the end of the frame.
+ if (db->Remaining() > frame_header_.payload_length) {
+ ADD_FAILURE() << "DecodeBuffer has too much data: " << db->Remaining()
+ << " > " << frame_header_.payload_length;
+ return DecodeStatus::kDecodeError;
+ }
+
+ // Prepare the payload decoder.
+ PreparePayloadDecoder();
+
+ // Reconstruct the FrameDecoderState, prepare the listener, and add it to
+ // the FrameDecoderState.
+ Http2DefaultReconstructObject(&frame_decoder_state_, RandomPtr());
+ frame_decoder_state_.set_listener(PrepareListener());
+
+ // Make sure that a listener was provided.
+ if (frame_decoder_state_.listener() == nullptr) {
+ ADD_FAILURE() << "PrepareListener must return a listener.";
+ return DecodeStatus::kDecodeError;
+ }
+
+ // Now that nothing in the payload decoder should be valid, inject the
+ // Http2FrameHeader whose payload we're about to decode. That header is the
+ // only state that a payload decoder should expect is valid when its Start
+ // method is called.
+ FrameDecoderStatePeer::set_frame_header(frame_header_, &frame_decoder_state_);
+ DecodeStatus status = StartDecodingPayload(db);
+ if (status != DecodeStatus::kDecodeInProgress) {
+ // Keep track of this so that a concrete test can verify that both fast
+ // and slow decoding paths have been tested.
+ ++fast_decode_count_;
+ }
+ return status;
+}
+
+DecodeStatus PayloadDecoderBaseTest::ResumeDecoding(DecodeBuffer* db) {
+ DVLOG(2) << "ResumeDecoding, db->Remaining=" << db->Remaining();
+ DecodeStatus status = ResumeDecodingPayload(db);
+ if (status != DecodeStatus::kDecodeInProgress) {
+ // Keep track of this so that a concrete test can verify that both fast
+ // and slow decoding paths have been tested.
+ ++slow_decode_count_;
+ }
+ return status;
+}
+
+::testing::AssertionResult
+PayloadDecoderBaseTest::DecodePayloadAndValidateSeveralWays(
+ Http2StringPiece payload,
+ Validator validator) {
+ VERIFY_TRUE(frame_header_is_set_);
+ // Cap the payload to be decoded at the declared payload length. This is
+ // required by the decoders' preconditions; they are designed on the
+ // assumption that they're never passed more than they're permitted to
+ // consume.
+ // Note that it is OK if the payload is too short; the validator may be
+ // designed to check for that.
+ if (payload.size() > frame_header_.payload_length) {
+ payload = Http2StringPiece(payload.data(), frame_header_.payload_length);
+ }
+ DecodeBuffer db(payload);
+ ResetDecodeSpeedCounters();
+ const bool kMayReturnZeroOnFirst = false;
+ return DecodeAndValidateSeveralWays(&db, kMayReturnZeroOnFirst, validator);
+}
+
+} // namespace test
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/payload_decoder_base_test_util.h b/http2/decoder/payload_decoders/payload_decoder_base_test_util.h
new file mode 100644
index 0000000..8297e70
--- /dev/null
+++ b/http2/decoder/payload_decoders/payload_decoder_base_test_util.h
@@ -0,0 +1,455 @@
+// Copyright 2016 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_PAYLOAD_DECODER_BASE_TEST_UTIL_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_
+
+// Base class for testing concrete payload decoder classes.
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_constants_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_reconstruct_object.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+// Base class for tests of payload decoders. Below this there is a templated
+// sub-class that adds a bunch of type specific features.
+class PayloadDecoderBaseTest : public RandomDecoderTest {
+ protected:
+ PayloadDecoderBaseTest();
+
+ // Virtual functions to be implemented by the test classes for the individual
+ // payload decoders...
+
+ // Start decoding the payload.
+ virtual DecodeStatus StartDecodingPayload(DecodeBuffer* db) = 0;
+
+ // Resume decoding the payload.
+ virtual DecodeStatus ResumeDecodingPayload(DecodeBuffer* db) = 0;
+
+ // In support of ensuring that we're really accessing and updating the
+ // decoder, prepare the decoder by, for example, overwriting the decoder.
+ virtual void PreparePayloadDecoder() = 0;
+
+ // Get the listener to be inserted into the FrameDecoderState, ready for
+ // listening (e.g. reset if it is a FramePartsCollector).
+ virtual Http2FrameDecoderListener* PrepareListener() = 0;
+
+ // Record a frame header for use on each call to StartDecoding.
+ void set_frame_header(const Http2FrameHeader& header) {
+ EXPECT_EQ(0, InvalidFlagMaskForFrameType(header.type) & header.flags);
+ if (!frame_header_is_set_ || frame_header_ != header) {
+ VLOG(2) << "set_frame_header: " << frame_header_;
+ }
+ frame_header_ = header;
+ frame_header_is_set_ = true;
+ }
+
+ FrameDecoderState* mutable_state() { return &frame_decoder_state_; }
+
+ // Randomize the payload decoder, sets the payload decoder's frame_header_,
+ // then start decoding the payload. Called by RandomDecoderTest. This method
+ // is final so that we can always perform certain actions when
+ // RandomDecoderTest starts the decoding of a payload, such as randomizing the
+ // the payload decoder, injecting the frame header and counting fast decoding
+ // cases. Sub-classes must implement StartDecodingPayload to perform their
+ // initial decoding of a frame's payload.
+ DecodeStatus StartDecoding(DecodeBuffer* db) final;
+
+ // Called by RandomDecoderTest. This method is final so that we can always
+ // perform certain actions when RandomDecoderTest calls it, such as counting
+ // slow decode cases. Sub-classes must implement ResumeDecodingPayload to
+ // continue decoding the frame's payload, which must not all be in one buffer.
+ DecodeStatus ResumeDecoding(DecodeBuffer* db) final;
+
+ // Given the specified payload (without the common frame header), decode
+ // it with several partitionings of the payload.
+ ::testing::AssertionResult DecodePayloadAndValidateSeveralWays(
+ Http2StringPiece payload,
+ Validator validator);
+
+ // TODO(jamessynge): Add helper method for verifying these are both non-zero,
+ // and call the new method from tests that expect successful decoding.
+ void ResetDecodeSpeedCounters() {
+ fast_decode_count_ = 0;
+ slow_decode_count_ = 0;
+ }
+
+ // Count of payloads that are full decoded by StartDecodingPayload, or that
+ // an error was detected by StartDecodingPayload.
+ size_t fast_decode_count_ = 0;
+
+ // Count of payloads that require calling ResumeDecodingPayload in order to
+ // decode them completely (or to detect an error during decoding).
+ size_t slow_decode_count_ = 0;
+
+ private:
+ bool frame_header_is_set_ = false;
+ Http2FrameHeader frame_header_;
+ FrameDecoderState frame_decoder_state_;
+};
+
+// Base class for payload decoders of type Decoder, with corresponding test
+// peer of type DecoderPeer, and using class Listener as the implementation
+// of Http2FrameDecoderListenerInterface to be used during decoding.
+// Typically Listener is a sub-class of FramePartsCollector.
+// SupportedFrameType is set to false only for UnknownPayloadDecoder.
+template <class Decoder,
+ class DecoderPeer,
+ class Listener,
+ bool SupportedFrameType = true>
+class AbstractPayloadDecoderTest : public PayloadDecoderBaseTest {
+ protected:
+ // An ApproveSize function returns true to approve decoding the specified
+ // size of payload, else false to skip that size. Typically used for negative
+ // tests; for example, decoding a SETTINGS frame at all sizes except for
+ // multiples of 6.
+ typedef std::function<bool(size_t size)> ApproveSize;
+
+ AbstractPayloadDecoderTest() {}
+
+ // These tests are in setup rather than the constructor for two reasons:
+ // 1) Constructors are not allowed to fail, so gUnit documents that EXPECT_*
+ // and ASSERT_* are not allowed in constructors, and should instead be in
+ // SetUp if they are needed before the body of the test is executed.
+ // 2) To allow the sub-class constructor to make any desired modifications to
+ // the DecoderPeer before these tests are executed; in particular,
+ // UnknownPayloadDecoderPeer has not got a fixed frame type, but it is
+ // instead set during the test's constructor.
+ void SetUp() override {
+ PayloadDecoderBaseTest::SetUp();
+
+ // Confirm that DecoderPeer et al returns sensible values. Using auto as the
+ // variable type so that no (narrowing) conversions take place that hide
+ // problems; i.e. if someone changes KnownFlagsMaskForFrameType so that it
+ // doesn't return a uint8, and has bits above the low-order 8 bits set, this
+ // bit of paranoia should detect the problem before we get too far.
+ auto frame_type = DecoderPeer::FrameType();
+ if (SupportedFrameType) {
+ EXPECT_TRUE(IsSupportedHttp2FrameType(frame_type)) << frame_type;
+ } else {
+ EXPECT_FALSE(IsSupportedHttp2FrameType(frame_type)) << frame_type;
+ }
+
+ auto known_flags = KnownFlagsMaskForFrameType(frame_type);
+ EXPECT_EQ(known_flags, known_flags & 0xff);
+
+ auto flags_to_avoid = DecoderPeer::FlagsAffectingPayloadDecoding();
+ EXPECT_EQ(flags_to_avoid, flags_to_avoid & known_flags);
+ }
+
+ void PreparePayloadDecoder() override {
+ Http2DefaultReconstructObject(&payload_decoder_, RandomPtr());
+ }
+
+ Http2FrameDecoderListener* PrepareListener() override {
+ listener_.Reset();
+ return &listener_;
+ }
+
+ // Returns random flags, but only those valid for the frame type, yet not
+ // those that the DecoderPeer says will affect the decoding of the payload
+ // (e.g. the PRIORTY flag on a HEADERS frame or PADDED on DATA frames).
+ uint8_t RandFlags() {
+ return Random().Rand8() &
+ KnownFlagsMaskForFrameType(DecoderPeer::FrameType()) &
+ ~DecoderPeer::FlagsAffectingPayloadDecoding();
+ }
+
+ // Start decoding the payload.
+ DecodeStatus StartDecodingPayload(DecodeBuffer* db) override {
+ DVLOG(2) << "StartDecodingPayload, db->Remaining=" << db->Remaining();
+ return payload_decoder_.StartDecodingPayload(mutable_state(), db);
+ }
+
+ // Resume decoding the payload.
+ DecodeStatus ResumeDecodingPayload(DecodeBuffer* db) override {
+ DVLOG(2) << "ResumeDecodingPayload, db->Remaining=" << db->Remaining();
+ return payload_decoder_.ResumeDecodingPayload(mutable_state(), db);
+ }
+
+ // Decode one frame's payload and confirm that the listener recorded the
+ // expected FrameParts instance, and only FrameParts instance. The payload
+ // will be decoded several times with different partitionings of the payload,
+ // and after each the validator will be called.
+ AssertionResult DecodePayloadAndValidateSeveralWays(
+ Http2StringPiece payload,
+ const FrameParts& expected) {
+ auto validator = [&expected, this]() -> AssertionResult {
+ VERIFY_FALSE(listener_.IsInProgress());
+ VERIFY_EQ(1u, listener_.size());
+ VERIFY_AND_RETURN_SUCCESS(expected.VerifyEquals(*listener_.frame(0)));
+ };
+ return PayloadDecoderBaseTest::DecodePayloadAndValidateSeveralWays(
+ payload, ValidateDoneAndEmpty(validator));
+ }
+
+ // Decode one frame's payload, expecting that the final status will be
+ // kDecodeError, and that OnFrameSizeError will have been called on the
+ // listener. The payload will be decoded several times with different
+ // partitionings of the payload. The type WrappedValidator is either
+ // RandomDecoderTest::Validator, RandomDecoderTest::NoArgValidator or
+ // std::nullptr_t (not extra validation).
+ template <typename WrappedValidator>
+ ::testing::AssertionResult VerifyDetectsFrameSizeError(
+ Http2StringPiece payload,
+ const Http2FrameHeader& header,
+ WrappedValidator wrapped_validator) {
+ set_frame_header(header);
+ // If wrapped_validator is not a RandomDecoderTest::Validator, make it so.
+ Validator validator = ToValidator(wrapped_validator);
+ // And wrap that validator in another which will check that we've reached
+ // the expected state of kDecodeError with OnFrameSizeError having been
+ // called by the payload decoder.
+ validator = [header, validator, this](
+ const DecodeBuffer& input,
+ DecodeStatus status) -> ::testing::AssertionResult {
+ DVLOG(2) << "VerifyDetectsFrameSizeError validator; status=" << status
+ << "; input.Remaining=" << input.Remaining();
+ VERIFY_EQ(DecodeStatus::kDecodeError, status);
+ VERIFY_FALSE(listener_.IsInProgress());
+ VERIFY_EQ(1u, listener_.size());
+ const FrameParts* frame = listener_.frame(0);
+ VERIFY_EQ(header, frame->GetFrameHeader());
+ VERIFY_TRUE(frame->GetHasFrameSizeError());
+ // Verify did not get OnPaddingTooLong, as we should only ever produce
+ // one of these two errors for a single frame.
+ VERIFY_FALSE(frame->GetOptMissingLength());
+ return validator(input, status);
+ };
+ VERIFY_AND_RETURN_SUCCESS(
+ PayloadDecoderBaseTest::DecodePayloadAndValidateSeveralWays(payload,
+ validator));
+ }
+
+ // Confirm that we get OnFrameSizeError when trying to decode unpadded_payload
+ // at all sizes from zero to unpadded_payload.size(), except those sizes not
+ // approved by approve_size.
+ // If total_pad_length is greater than zero, then that amount of padding
+ // is added to the payload (including the Pad Length field).
+ // The flags will be required_flags, PADDED if total_pad_length > 0, and some
+ // randomly selected flag bits not excluded by FlagsAffectingPayloadDecoding.
+ ::testing::AssertionResult VerifyDetectsMultipleFrameSizeErrors(
+ uint8_t required_flags,
+ Http2StringPiece unpadded_payload,
+ ApproveSize approve_size,
+ int total_pad_length) {
+ // required_flags should come from those that are defined for the frame
+ // type AND are those that affect the decoding of the payload (otherwise,
+ // the flag shouldn't be required).
+ Http2FrameType frame_type = DecoderPeer::FrameType();
+ VERIFY_EQ(required_flags,
+ required_flags & KnownFlagsMaskForFrameType(frame_type));
+ VERIFY_EQ(required_flags,
+ required_flags & DecoderPeer::FlagsAffectingPayloadDecoding());
+
+ if (0 !=
+ (Http2FrameFlag::PADDED & KnownFlagsMaskForFrameType(frame_type))) {
+ // Frame type supports padding.
+ if (total_pad_length == 0) {
+ required_flags &= ~Http2FrameFlag::PADDED;
+ } else {
+ required_flags |= Http2FrameFlag::PADDED;
+ }
+ } else {
+ VERIFY_EQ(0, total_pad_length);
+ }
+
+ bool validated = false;
+ for (size_t real_payload_size = 0;
+ real_payload_size <= unpadded_payload.size(); ++real_payload_size) {
+ if (approve_size != nullptr && !approve_size(real_payload_size)) {
+ continue;
+ }
+ VLOG(1) << "real_payload_size=" << real_payload_size;
+ uint8_t flags = required_flags | RandFlags();
+ Http2FrameBuilder fb;
+ if (total_pad_length > 0) {
+ // total_pad_length_ includes the size of the Pad Length field, and thus
+ // ranges from 0 (no PADDED flag) to 256 (Pad Length == 255).
+ fb.AppendUInt8(total_pad_length - 1);
+ }
+ // Append a subset of the unpadded_payload, which the decoder should
+ // determine is not a valid amount.
+ fb.Append(unpadded_payload.substr(0, real_payload_size));
+ if (total_pad_length > 0) {
+ fb.AppendZeroes(total_pad_length - 1);
+ }
+ // We choose a random stream id because the payload decoders aren't
+ // checking stream ids.
+ uint32_t stream_id = RandStreamId();
+ Http2FrameHeader header(fb.size(), frame_type, flags, stream_id);
+ VERIFY_SUCCESS(VerifyDetectsFrameSizeError(fb.buffer(), header, nullptr));
+ validated = true;
+ }
+ VERIFY_TRUE(validated);
+ return ::testing::AssertionSuccess();
+ }
+
+ // As above, but for frames without padding.
+ ::testing::AssertionResult VerifyDetectsFrameSizeError(
+ uint8_t required_flags,
+ Http2StringPiece unpadded_payload,
+ const ApproveSize& approve_size) {
+ Http2FrameType frame_type = DecoderPeer::FrameType();
+ uint8_t known_flags = KnownFlagsMaskForFrameType(frame_type);
+ VERIFY_EQ(0, known_flags & Http2FrameFlag::PADDED);
+ VERIFY_EQ(0, required_flags & Http2FrameFlag::PADDED);
+ VERIFY_AND_RETURN_SUCCESS(VerifyDetectsMultipleFrameSizeErrors(
+ required_flags, unpadded_payload, approve_size, 0));
+ }
+
+ Listener listener_;
+ union {
+ // Confirm at compile time that Decoder can be in an anonymous union,
+ // i.e. complain loudly if Decoder has members that prevent this, as it
+ // becomes annoying and possibly difficult to deal with non-anonymous
+ // unions and such union members.
+ Decoder payload_decoder_;
+ };
+};
+
+// A base class for tests parameterized by the total number of bytes of
+// padding, including the Pad Length field (i.e. a total_pad_length of 0
+// means unpadded as there is then no room for the Pad Length field).
+// The frame type must support padding.
+template <class Decoder, class DecoderPeer, class Listener>
+class AbstractPaddablePayloadDecoderTest
+ : public AbstractPayloadDecoderTest<Decoder, DecoderPeer, Listener>,
+ public ::testing::WithParamInterface<int> {
+ typedef AbstractPayloadDecoderTest<Decoder, DecoderPeer, Listener> Base;
+
+ protected:
+ using Base::listener_;
+ using Base::Random;
+ using Base::RandStreamId;
+ using Base::set_frame_header;
+ typedef typename Base::Validator Validator;
+
+ AbstractPaddablePayloadDecoderTest() : total_pad_length_(GetParam()) {
+ LOG(INFO) << "total_pad_length_ = " << total_pad_length_;
+ }
+
+ // Note that total_pad_length_ includes the size of the Pad Length field,
+ // and thus ranges from 0 (no PADDED flag) to 256 (Pad Length == 255).
+ bool IsPadded() const { return total_pad_length_ > 0; }
+
+ // Value of the Pad Length field. Only call if IsPadded.
+ size_t pad_length() const {
+ EXPECT_TRUE(IsPadded());
+ return total_pad_length_ - 1;
+ }
+
+ // Clear the frame builder and add the Pad Length field if appropriate.
+ void Reset() {
+ frame_builder_ = Http2FrameBuilder();
+ if (IsPadded()) {
+ frame_builder_.AppendUInt8(pad_length());
+ }
+ }
+
+ void MaybeAppendTrailingPadding() {
+ if (IsPadded()) {
+ frame_builder_.AppendZeroes(pad_length());
+ }
+ }
+
+ uint8_t RandFlags() {
+ uint8_t flags = Base::RandFlags();
+ if (IsPadded()) {
+ flags |= Http2FrameFlag::PADDED;
+ } else {
+ flags &= ~Http2FrameFlag::PADDED;
+ }
+ return flags;
+ }
+
+ // Verify that we get OnPaddingTooLong when decoding payload, and that the
+ // amount of missing padding is as specified. header.IsPadded must be true,
+ // and the payload must be empty or the PadLength field must be too large.
+ ::testing::AssertionResult VerifyDetectsPaddingTooLong(
+ Http2StringPiece payload,
+ const Http2FrameHeader& header,
+ size_t expected_missing_length) {
+ set_frame_header(header);
+ auto& listener = listener_;
+ Validator validator =
+ [header, expected_missing_length, &listener](
+ const DecodeBuffer& input,
+ DecodeStatus status) -> ::testing::AssertionResult {
+ VERIFY_EQ(DecodeStatus::kDecodeError, status);
+ VERIFY_FALSE(listener.IsInProgress());
+ VERIFY_EQ(1u, listener.size());
+ const FrameParts* frame = listener.frame(0);
+ VERIFY_EQ(header, frame->GetFrameHeader());
+ VERIFY_TRUE(frame->GetOptMissingLength());
+ VERIFY_EQ(expected_missing_length, frame->GetOptMissingLength().value());
+ // Verify did not get OnFrameSizeError.
+ VERIFY_FALSE(frame->GetHasFrameSizeError());
+ return ::testing::AssertionSuccess();
+ };
+ VERIFY_AND_RETURN_SUCCESS(
+ PayloadDecoderBaseTest::DecodePayloadAndValidateSeveralWays(payload,
+ validator));
+ }
+
+ // Verifies that we get OnPaddingTooLong for a padded frame payload whose
+ // (randomly selected) payload length is less than total_pad_length_.
+ // Flags will be selected at random, except PADDED will be set and
+ // flags_to_avoid will not be set. The stream id is selected at random.
+ ::testing::AssertionResult VerifyDetectsPaddingTooLong() {
+ uint8_t flags = RandFlags() | Http2FrameFlag::PADDED;
+
+ // Create an all padding payload for total_pad_length_.
+ int payload_length = 0;
+ Http2FrameBuilder fb;
+ if (IsPadded()) {
+ fb.AppendUInt8(pad_length());
+ fb.AppendZeroes(pad_length());
+ VLOG(1) << "fb.size=" << fb.size();
+ // Pick a random length for the payload that is shorter than neccesary.
+ payload_length = Random().Uniform(fb.size());
+ }
+
+ VLOG(1) << "payload_length=" << payload_length;
+ Http2String payload = fb.buffer().substr(0, payload_length);
+
+ // The missing length is the amount we cut off the end, unless
+ // payload_length is zero, in which case the decoder knows only that 1
+ // byte, the Pad Length field, is missing.
+ size_t missing_length =
+ payload_length == 0 ? 1 : fb.size() - payload_length;
+ VLOG(1) << "missing_length=" << missing_length;
+
+ const Http2FrameHeader header(payload_length, DecoderPeer::FrameType(),
+ flags, RandStreamId());
+ VERIFY_AND_RETURN_SUCCESS(
+ VerifyDetectsPaddingTooLong(payload, header, missing_length));
+ }
+
+ // total_pad_length_ includes the size of the Pad Length field, and thus
+ // ranges from 0 (no PADDED flag) to 256 (Pad Length == 255).
+ const size_t total_pad_length_;
+ Http2FrameBuilder frame_builder_;
+};
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_
diff --git a/http2/decoder/payload_decoders/ping_payload_decoder.cc b/http2/decoder/payload_decoders/ping_payload_decoder.cc
new file mode 100644
index 0000000..8d98046
--- /dev/null
+++ b/http2/decoder/payload_decoders/ping_payload_decoder.cc
@@ -0,0 +1,89 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+
+namespace http2 {
+namespace {
+constexpr auto kOpaqueSize = Http2PingFields::EncodedSize();
+}
+
+DecodeStatus PingPayloadDecoder::StartDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db) {
+ const Http2FrameHeader& frame_header = state->frame_header();
+ const uint32_t total_length = frame_header.payload_length;
+
+ DVLOG(2) << "PingPayloadDecoder::StartDecodingPayload: " << frame_header;
+ DCHECK_EQ(Http2FrameType::PING, frame_header.type);
+ DCHECK_LE(db->Remaining(), total_length);
+ DCHECK_EQ(0, frame_header.flags & ~(Http2FrameFlag::ACK));
+
+ // Is the payload entirely in the decode buffer and is it the correct size?
+ // Given the size of the header and payload (17 bytes total), this is most
+ // likely the case the vast majority of the time.
+ if (db->Remaining() == kOpaqueSize && total_length == kOpaqueSize) {
+ // Special case this situation as it allows us to avoid any copying;
+ // the other path makes two copies, first into the buffer in
+ // Http2StructureDecoder as it accumulates the 8 bytes of opaque data,
+ // and a second copy into the Http2PingFields member of in this class.
+ // This supports the claim that this decoder is (mostly) non-buffering.
+ static_assert(sizeof(Http2PingFields) == kOpaqueSize,
+ "If not, then can't enter this block!");
+ auto* ping = reinterpret_cast<const Http2PingFields*>(db->cursor());
+ if (frame_header.IsAck()) {
+ state->listener()->OnPingAck(frame_header, *ping);
+ } else {
+ state->listener()->OnPing(frame_header, *ping);
+ }
+ db->AdvanceCursor(kOpaqueSize);
+ return DecodeStatus::kDecodeDone;
+ }
+ state->InitializeRemainders();
+ return HandleStatus(
+ state, state->StartDecodingStructureInPayload(&ping_fields_, db));
+}
+
+DecodeStatus PingPayloadDecoder::ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "ResumeDecodingPayload: remaining_payload="
+ << state->remaining_payload();
+ DCHECK_EQ(Http2FrameType::PING, state->frame_header().type);
+ DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+ return HandleStatus(
+ state, state->ResumeDecodingStructureInPayload(&ping_fields_, db));
+}
+
+DecodeStatus PingPayloadDecoder::HandleStatus(FrameDecoderState* state,
+ DecodeStatus status) {
+ DVLOG(2) << "HandleStatus: status=" << status
+ << "; remaining_payload=" << state->remaining_payload();
+ if (status == DecodeStatus::kDecodeDone) {
+ if (state->remaining_payload() == 0) {
+ const Http2FrameHeader& frame_header = state->frame_header();
+ if (frame_header.IsAck()) {
+ state->listener()->OnPingAck(frame_header, ping_fields_);
+ } else {
+ state->listener()->OnPing(frame_header, ping_fields_);
+ }
+ return DecodeStatus::kDecodeDone;
+ }
+ // Payload is too long.
+ return state->ReportFrameSizeError();
+ }
+ // Not done decoding the structure. Either we've got more payload to decode,
+ // or we've run out because the payload is too short.
+ DCHECK(
+ (status == DecodeStatus::kDecodeInProgress &&
+ state->remaining_payload() > 0) ||
+ (status == DecodeStatus::kDecodeError && state->remaining_payload() == 0))
+ << "\n status=" << status
+ << "; remaining_payload=" << state->remaining_payload();
+ return status;
+}
+
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/ping_payload_decoder.h b/http2/decoder/payload_decoders/ping_payload_decoder.h
new file mode 100644
index 0000000..84704fb
--- /dev/null
+++ b/http2/decoder/payload_decoders/ping_payload_decoder.h
@@ -0,0 +1,43 @@
+// Copyright 2016 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_PING_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PING_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a PING frame; for the RFC, see:
+// http://httpwg.org/specs/rfc7540.html#PING
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class PingPayloadDecoderPeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE PingPayloadDecoder {
+ public:
+ // Starts the decoding of a PING 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 PING frame's payload that has been split across
+ // decode buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+
+ private:
+ friend class test::PingPayloadDecoderPeer;
+
+ DecodeStatus HandleStatus(FrameDecoderState* state, DecodeStatus status);
+
+ Http2PingFields ping_fields_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PING_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/ping_payload_decoder_test.cc b/http2/decoder/payload_decoders/ping_payload_decoder_test.cc
new file mode 100644
index 0000000..34833b2
--- /dev/null
+++ b/http2/decoder/payload_decoders/ping_payload_decoder_test.cc
@@ -0,0 +1,110 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+class PingPayloadDecoderPeer {
+ public:
+ static constexpr Http2FrameType FrameType() { return Http2FrameType::PING; }
+
+ // 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 OnPing(const Http2FrameHeader& header,
+ const Http2PingFields& ping) override {
+ VLOG(1) << "OnPing: " << header << "; " << ping;
+ StartAndEndFrame(header)->OnPing(header, ping);
+ }
+
+ void OnPingAck(const Http2FrameHeader& header,
+ const Http2PingFields& ping) override {
+ VLOG(1) << "OnPingAck: " << header << "; " << ping;
+ StartAndEndFrame(header)->OnPingAck(header, ping);
+ }
+
+ void OnFrameSizeError(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnFrameSizeError: " << header;
+ FrameError(header)->OnFrameSizeError(header);
+ }
+};
+
+class PingPayloadDecoderTest
+ : public AbstractPayloadDecoderTest<PingPayloadDecoder,
+ PingPayloadDecoderPeer,
+ Listener> {
+ protected:
+ Http2PingFields RandPingFields() {
+ Http2PingFields fields;
+ test::Randomize(&fields, RandomPtr());
+ return fields;
+ }
+};
+
+// Confirm we get an error if the payload is not the correct size to hold
+// exactly one Http2PingFields.
+TEST_F(PingPayloadDecoderTest, WrongSize) {
+ auto approve_size = [](size_t size) {
+ return size != Http2PingFields::EncodedSize();
+ };
+ Http2FrameBuilder fb;
+ fb.Append(RandPingFields());
+ fb.Append(RandPingFields());
+ fb.Append(RandPingFields());
+ EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size));
+}
+
+TEST_F(PingPayloadDecoderTest, Ping) {
+ for (int n = 0; n < 100; ++n) {
+ Http2PingFields fields = RandPingFields();
+ Http2FrameBuilder fb;
+ fb.Append(fields);
+ Http2FrameHeader header(fb.size(), Http2FrameType::PING,
+ RandFlags() & ~Http2FrameFlag::ACK, RandStreamId());
+ set_frame_header(header);
+ FrameParts expected(header);
+ expected.SetOptPing(fields);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+ }
+}
+
+TEST_F(PingPayloadDecoderTest, PingAck) {
+ for (int n = 0; n < 100; ++n) {
+ Http2PingFields fields;
+ Randomize(&fields, RandomPtr());
+ Http2FrameBuilder fb;
+ fb.Append(fields);
+ Http2FrameHeader header(fb.size(), Http2FrameType::PING,
+ RandFlags() | Http2FrameFlag::ACK, RandStreamId());
+ set_frame_header(header);
+ FrameParts expected(header);
+ expected.SetOptPing(fields);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/priority_payload_decoder.cc b/http2/decoder/payload_decoders/priority_payload_decoder.cc
new file mode 100644
index 0000000..7be1c95
--- /dev/null
+++ b/http2/decoder/payload_decoders/priority_payload_decoder.cc
@@ -0,0 +1,64 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+namespace http2 {
+
+DecodeStatus PriorityPayloadDecoder::StartDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "PriorityPayloadDecoder::StartDecodingPayload: "
+ << state->frame_header();
+ DCHECK_EQ(Http2FrameType::PRIORITY, state->frame_header().type);
+ DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+ // PRIORITY frames have no flags.
+ DCHECK_EQ(0, state->frame_header().flags);
+ state->InitializeRemainders();
+ return HandleStatus(
+ state, state->StartDecodingStructureInPayload(&priority_fields_, db));
+}
+
+DecodeStatus PriorityPayloadDecoder::ResumeDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "PriorityPayloadDecoder::ResumeDecodingPayload"
+ << " remaining_payload=" << state->remaining_payload()
+ << " db->Remaining=" << db->Remaining();
+ DCHECK_EQ(Http2FrameType::PRIORITY, state->frame_header().type);
+ DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+ return HandleStatus(
+ state, state->ResumeDecodingStructureInPayload(&priority_fields_, db));
+}
+
+DecodeStatus PriorityPayloadDecoder::HandleStatus(FrameDecoderState* state,
+ DecodeStatus status) {
+ if (status == DecodeStatus::kDecodeDone) {
+ if (state->remaining_payload() == 0) {
+ state->listener()->OnPriorityFrame(state->frame_header(),
+ priority_fields_);
+ return DecodeStatus::kDecodeDone;
+ }
+ // Payload is too long.
+ return state->ReportFrameSizeError();
+ }
+ // 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();
+ return status;
+}
+
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/priority_payload_decoder.h b/http2/decoder/payload_decoders/priority_payload_decoder.h
new file mode 100644
index 0000000..921eefe
--- /dev/null
+++ b/http2/decoder/payload_decoders/priority_payload_decoder.h
@@ -0,0 +1,44 @@
+// Copyright 2016 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_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PRIORITY_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a PRIORITY frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class PriorityPayloadDecoderPeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE PriorityPayloadDecoder {
+ public:
+ // Starts the decoding of a PRIORITY 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 frame that has been split across decode
+ // buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+
+ private:
+ friend class test::PriorityPayloadDecoderPeer;
+
+ // Determines whether to report the PRIORITY to the listener, wait for more
+ // input, or to report a Frame Size Error.
+ DecodeStatus HandleStatus(FrameDecoderState* state, DecodeStatus status);
+
+ Http2PriorityFields priority_fields_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PRIORITY_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/priority_payload_decoder_test.cc b/http2/decoder/payload_decoders/priority_payload_decoder_test.cc
new file mode 100644
index 0000000..4e44eba
--- /dev/null
+++ b/http2/decoder/payload_decoders/priority_payload_decoder_test.cc
@@ -0,0 +1,90 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+class PriorityPayloadDecoderPeer {
+ public:
+ static constexpr Http2FrameType FrameType() {
+ return Http2FrameType::PRIORITY;
+ }
+
+ // 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 OnPriorityFrame(const Http2FrameHeader& header,
+ const Http2PriorityFields& priority_fields) override {
+ VLOG(1) << "OnPriority: " << header << "; " << priority_fields;
+ StartAndEndFrame(header)->OnPriorityFrame(header, priority_fields);
+ }
+
+ void OnFrameSizeError(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnFrameSizeError: " << header;
+ FrameError(header)->OnFrameSizeError(header);
+ }
+};
+
+class PriorityPayloadDecoderTest
+ : public AbstractPayloadDecoderTest<PriorityPayloadDecoder,
+ PriorityPayloadDecoderPeer,
+ Listener> {
+ protected:
+ Http2PriorityFields RandPriorityFields() {
+ Http2PriorityFields fields;
+ test::Randomize(&fields, RandomPtr());
+ return fields;
+ }
+};
+
+// Confirm we get an error if the payload is not the correct size to hold
+// exactly one Http2PriorityFields.
+TEST_F(PriorityPayloadDecoderTest, WrongSize) {
+ auto approve_size = [](size_t size) {
+ return size != Http2PriorityFields::EncodedSize();
+ };
+ Http2FrameBuilder fb;
+ fb.Append(RandPriorityFields());
+ fb.Append(RandPriorityFields());
+ EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size));
+}
+
+TEST_F(PriorityPayloadDecoderTest, VariousPayloads) {
+ for (int n = 0; n < 100; ++n) {
+ Http2PriorityFields fields = RandPriorityFields();
+ Http2FrameBuilder fb;
+ fb.Append(fields);
+ Http2FrameHeader header(fb.size(), Http2FrameType::PRIORITY, RandFlags(),
+ RandStreamId());
+ set_frame_header(header);
+ FrameParts expected(header);
+ expected.SetOptPriority(fields);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/push_promise_payload_decoder.cc b/http2/decoder/payload_decoders/push_promise_payload_decoder.cc
new file mode 100644
index 0000000..cec1c07
--- /dev/null
+++ b/http2/decoder/payload_decoders/push_promise_payload_decoder.cc
@@ -0,0 +1,172 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+
+namespace http2 {
+
+std::ostream& operator<<(std::ostream& out,
+ PushPromisePayloadDecoder::PayloadState v) {
+ switch (v) {
+ case PushPromisePayloadDecoder::PayloadState::kReadPadLength:
+ return out << "kReadPadLength";
+ case PushPromisePayloadDecoder::PayloadState::
+ kStartDecodingPushPromiseFields:
+ return out << "kStartDecodingPushPromiseFields";
+ case PushPromisePayloadDecoder::PayloadState::kReadPayload:
+ return out << "kReadPayload";
+ case PushPromisePayloadDecoder::PayloadState::kSkipPadding:
+ return out << "kSkipPadding";
+ case PushPromisePayloadDecoder::PayloadState::
+ kResumeDecodingPushPromiseFields:
+ return out << "kResumeDecodingPushPromiseFields";
+ }
+ return out << static_cast<int>(v);
+}
+
+DecodeStatus PushPromisePayloadDecoder::StartDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ const Http2FrameHeader& frame_header = state->frame_header();
+ const uint32_t total_length = frame_header.payload_length;
+
+ DVLOG(2) << "PushPromisePayloadDecoder::StartDecodingPayload: "
+ << frame_header;
+
+ DCHECK_EQ(Http2FrameType::PUSH_PROMISE, frame_header.type);
+ DCHECK_LE(db->Remaining(), total_length);
+ DCHECK_EQ(0, frame_header.flags &
+ ~(Http2FrameFlag::END_HEADERS | Http2FrameFlag::PADDED));
+
+ if (!frame_header.IsPadded()) {
+ // If it turns out that PUSH_PROMISE frames without padding are sufficiently
+ // common, and that they are usually short enough that they fit entirely
+ // into one DecodeBuffer, we can detect that here and implement a special
+ // case, avoiding the state machine in ResumeDecodingPayload.
+ payload_state_ = PayloadState::kStartDecodingPushPromiseFields;
+ } else {
+ payload_state_ = PayloadState::kReadPadLength;
+ }
+ state->InitializeRemainders();
+ return ResumeDecodingPayload(state, db);
+}
+
+DecodeStatus PushPromisePayloadDecoder::ResumeDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "UnknownPayloadDecoder::ResumeDecodingPayload"
+ << " remaining_payload=" << state->remaining_payload()
+ << " db->Remaining=" << db->Remaining();
+
+ const Http2FrameHeader& frame_header = state->frame_header();
+ DCHECK_EQ(Http2FrameType::PUSH_PROMISE, frame_header.type);
+ DCHECK_LE(state->remaining_payload(), frame_header.payload_length);
+ DCHECK_LE(db->Remaining(), frame_header.payload_length);
+
+ DecodeStatus status;
+ while (true) {
+ DVLOG(2)
+ << "PushPromisePayloadDecoder::ResumeDecodingPayload payload_state_="
+ << payload_state_;
+ switch (payload_state_) {
+ case PayloadState::kReadPadLength:
+ DCHECK_EQ(state->remaining_payload(), frame_header.payload_length);
+ // ReadPadLength handles the OnPadLength callback, and updating the
+ // remaining_payload and remaining_padding fields. If the amount of
+ // padding is too large to fit in the frame's payload, ReadPadLength
+ // instead calls OnPaddingTooLong and returns kDecodeError.
+ // Suppress the call to OnPadLength because we haven't yet called
+ // OnPushPromiseStart, which needs to wait until we've decoded the
+ // Promised Stream ID.
+ status = state->ReadPadLength(db, /*report_pad_length*/ false);
+ if (status != DecodeStatus::kDecodeDone) {
+ payload_state_ = PayloadState::kReadPadLength;
+ return status;
+ }
+ HTTP2_FALLTHROUGH;
+
+ case PayloadState::kStartDecodingPushPromiseFields:
+ status =
+ state->StartDecodingStructureInPayload(&push_promise_fields_, db);
+ if (status != DecodeStatus::kDecodeDone) {
+ payload_state_ = PayloadState::kResumeDecodingPushPromiseFields;
+ return status;
+ }
+ // Finished decoding the Promised Stream ID. Can now tell the listener
+ // that we're starting to decode a PUSH_PROMISE frame.
+ ReportPushPromise(state);
+ HTTP2_FALLTHROUGH;
+
+ case PayloadState::kReadPayload:
+ DCHECK_LT(state->remaining_payload(), frame_header.payload_length);
+ DCHECK_LE(state->remaining_payload(),
+ frame_header.payload_length -
+ Http2PushPromiseFields::EncodedSize());
+ DCHECK_LE(
+ state->remaining_payload(),
+ frame_header.payload_length -
+ Http2PushPromiseFields::EncodedSize() -
+ (frame_header.IsPadded() ? (1 + state->remaining_padding())
+ : 0));
+ {
+ size_t avail = state->AvailablePayload(db);
+ state->listener()->OnHpackFragment(db->cursor(), avail);
+ db->AdvanceCursor(avail);
+ state->ConsumePayload(avail);
+ }
+ if (state->remaining_payload() > 0) {
+ payload_state_ = PayloadState::kReadPayload;
+ return DecodeStatus::kDecodeInProgress;
+ }
+ HTTP2_FALLTHROUGH;
+
+ case PayloadState::kSkipPadding:
+ // SkipPadding handles the OnPadding callback.
+ if (state->SkipPadding(db)) {
+ state->listener()->OnPushPromiseEnd();
+ return DecodeStatus::kDecodeDone;
+ }
+ payload_state_ = PayloadState::kSkipPadding;
+ return DecodeStatus::kDecodeInProgress;
+
+ case PayloadState::kResumeDecodingPushPromiseFields:
+ status =
+ state->ResumeDecodingStructureInPayload(&push_promise_fields_, db);
+ if (status == DecodeStatus::kDecodeDone) {
+ // Finished decoding the Promised Stream ID. Can now tell the listener
+ // that we're starting to decode a PUSH_PROMISE frame.
+ ReportPushPromise(state);
+ payload_state_ = PayloadState::kReadPayload;
+ continue;
+ }
+ payload_state_ = PayloadState::kResumeDecodingPushPromiseFields;
+ return status;
+ }
+ HTTP2_BUG << "PayloadState: " << payload_state_;
+ }
+}
+
+void PushPromisePayloadDecoder::ReportPushPromise(FrameDecoderState* state) {
+ const Http2FrameHeader& frame_header = state->frame_header();
+ if (frame_header.IsPadded()) {
+ state->listener()->OnPushPromiseStart(frame_header, push_promise_fields_,
+ 1 + state->remaining_padding());
+ } else {
+ state->listener()->OnPushPromiseStart(frame_header, push_promise_fields_,
+ 0);
+ }
+}
+
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/push_promise_payload_decoder.h b/http2/decoder/payload_decoders/push_promise_payload_decoder.h
new file mode 100644
index 0000000..2db9cb3
--- /dev/null
+++ b/http2/decoder/payload_decoders/push_promise_payload_decoder.h
@@ -0,0 +1,66 @@
+// Copyright 2016 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_PUSH_PROMISE_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PUSH_PROMISE_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a PUSH_PROMISE frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class PushPromisePayloadDecoderPeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE PushPromisePayloadDecoder {
+ public:
+ // States during decoding of a PUSH_PROMISE frame.
+ enum class PayloadState {
+ // The frame is padded and we need to read the PAD_LENGTH field (1 byte).
+ kReadPadLength,
+
+ // Ready to start decoding the fixed size fields of the PUSH_PROMISE
+ // frame into push_promise_fields_.
+ kStartDecodingPushPromiseFields,
+
+ // The decoder has already called OnPushPromiseStart, and is now reporting
+ // the HPACK block fragment to the listener's OnHpackFragment method.
+ kReadPayload,
+
+ // The decoder has finished with the HPACK block fragment, and is now
+ // ready to skip the trailing padding, if the frame has any.
+ kSkipPadding,
+
+ // The fixed size fields weren't all available when the decoder first tried
+ // to decode them (state kStartDecodingPushPromiseFields); this state
+ // resumes the decoding when ResumeDecodingPayload is called later.
+ kResumeDecodingPushPromiseFields,
+ };
+
+ // Starts the decoding of a PUSH_PROMISE 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 PUSH_PROMISE frame's payload that has been split across
+ // decode buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+
+ private:
+ friend class test::PushPromisePayloadDecoderPeer;
+
+ void ReportPushPromise(FrameDecoderState* state);
+
+ PayloadState payload_state_;
+ Http2PushPromiseFields push_promise_fields_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PUSH_PROMISE_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/push_promise_payload_decoder_test.cc b/http2/decoder/payload_decoders/push_promise_payload_decoder_test.cc
new file mode 100644
index 0000000..9d55a80
--- /dev/null
+++ b/http2/decoder/payload_decoders/push_promise_payload_decoder_test.cc
@@ -0,0 +1,137 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+// Provides friend access to an instance of the payload decoder, and also
+// provides info to aid in testing.
+class PushPromisePayloadDecoderPeer {
+ public:
+ static constexpr Http2FrameType FrameType() {
+ return Http2FrameType::PUSH_PROMISE;
+ }
+
+ // 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 Http2FrameFlag::PADDED;
+ }
+};
+
+namespace {
+
+// Listener listens for only those methods expected by the payload decoder
+// under test, and forwards them onto the FrameParts instance for the current
+// frame.
+struct Listener : public FramePartsCollector {
+ void OnPushPromiseStart(const Http2FrameHeader& header,
+ const Http2PushPromiseFields& promise,
+ size_t total_padding_length) override {
+ VLOG(1) << "OnPushPromiseStart header: " << header
+ << " promise: " << promise
+ << " total_padding_length: " << total_padding_length;
+ EXPECT_EQ(Http2FrameType::PUSH_PROMISE, header.type);
+ StartFrame(header)->OnPushPromiseStart(header, promise,
+ total_padding_length);
+ }
+
+ void OnHpackFragment(const char* data, size_t len) override {
+ VLOG(1) << "OnHpackFragment: len=" << len;
+ CurrentFrame()->OnHpackFragment(data, len);
+ }
+
+ void OnPushPromiseEnd() override {
+ VLOG(1) << "OnPushPromiseEnd";
+ EndFrame()->OnPushPromiseEnd();
+ }
+
+ void OnPadding(const char* padding, size_t skipped_length) override {
+ VLOG(1) << "OnPadding: " << skipped_length;
+ CurrentFrame()->OnPadding(padding, skipped_length);
+ }
+
+ void OnPaddingTooLong(const Http2FrameHeader& header,
+ size_t missing_length) override {
+ VLOG(1) << "OnPaddingTooLong: " << header
+ << "; missing_length: " << missing_length;
+ FrameError(header)->OnPaddingTooLong(header, missing_length);
+ }
+
+ void OnFrameSizeError(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnFrameSizeError: " << header;
+ FrameError(header)->OnFrameSizeError(header);
+ }
+};
+
+class PushPromisePayloadDecoderTest
+ : public AbstractPaddablePayloadDecoderTest<PushPromisePayloadDecoder,
+ PushPromisePayloadDecoderPeer,
+ Listener> {};
+
+INSTANTIATE_TEST_CASE_P(VariousPadLengths,
+ PushPromisePayloadDecoderTest,
+ ::testing::Values(0, 1, 2, 3, 4, 254, 255, 256));
+
+// Payload contains the required Http2PushPromiseFields, followed by some
+// (fake) HPACK payload.
+TEST_P(PushPromisePayloadDecoderTest, VariousHpackPayloadSizes) {
+ for (size_t hpack_size : {0, 1, 2, 3, 255, 256, 1024}) {
+ LOG(INFO) << "########### hpack_size = " << hpack_size << " ###########";
+ Reset();
+ Http2String hpack_payload = Random().RandString(hpack_size);
+ Http2PushPromiseFields push_promise{RandStreamId()};
+ frame_builder_.Append(push_promise);
+ frame_builder_.Append(hpack_payload);
+ MaybeAppendTrailingPadding();
+ Http2FrameHeader frame_header(frame_builder_.size(),
+ Http2FrameType::PUSH_PROMISE, RandFlags(),
+ RandStreamId());
+ set_frame_header(frame_header);
+ FrameParts expected(frame_header, hpack_payload, total_pad_length_);
+ expected.SetOptPushPromise(push_promise);
+ EXPECT_TRUE(
+ DecodePayloadAndValidateSeveralWays(frame_builder_.buffer(), expected));
+ }
+}
+
+// Confirm we get an error if the payload is not long enough for the required
+// portion of the payload, regardless of the amount of (valid) padding.
+TEST_P(PushPromisePayloadDecoderTest, Truncated) {
+ auto approve_size = [](size_t size) {
+ return size != Http2PushPromiseFields::EncodedSize();
+ };
+ Http2PushPromiseFields push_promise{RandStreamId()};
+ Http2FrameBuilder fb;
+ fb.Append(push_promise);
+ EXPECT_TRUE(VerifyDetectsMultipleFrameSizeErrors(0, fb.buffer(), approve_size,
+ total_pad_length_));
+}
+
+// Confirm we get an error if the PADDED flag is set but the payload is not
+// long enough to hold even the Pad Length amount of padding.
+TEST_P(PushPromisePayloadDecoderTest, PaddingTooLong) {
+ EXPECT_TRUE(VerifyDetectsPaddingTooLong());
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/rst_stream_payload_decoder.cc b/http2/decoder/payload_decoders/rst_stream_payload_decoder.cc
new file mode 100644
index 0000000..c39a16a
--- /dev/null
+++ b/http2/decoder/payload_decoders/rst_stream_payload_decoder.cc
@@ -0,0 +1,66 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+namespace http2 {
+
+DecodeStatus RstStreamPayloadDecoder::StartDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "RstStreamPayloadDecoder::StartDecodingPayload: "
+ << state->frame_header();
+ DCHECK_EQ(Http2FrameType::RST_STREAM, state->frame_header().type);
+ DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+ // RST_STREAM has no flags.
+ DCHECK_EQ(0, state->frame_header().flags);
+ state->InitializeRemainders();
+ return HandleStatus(
+ state, state->StartDecodingStructureInPayload(&rst_stream_fields_, db));
+}
+
+DecodeStatus RstStreamPayloadDecoder::ResumeDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "RstStreamPayloadDecoder::ResumeDecodingPayload"
+ << " remaining_payload=" << state->remaining_payload()
+ << " db->Remaining=" << db->Remaining();
+ DCHECK_EQ(Http2FrameType::RST_STREAM, state->frame_header().type);
+ DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+ return HandleStatus(
+ state, state->ResumeDecodingStructureInPayload(&rst_stream_fields_, db));
+}
+
+DecodeStatus RstStreamPayloadDecoder::HandleStatus(FrameDecoderState* state,
+ DecodeStatus status) {
+ DVLOG(2) << "HandleStatus: status=" << status
+ << "; remaining_payload=" << state->remaining_payload();
+ if (status == DecodeStatus::kDecodeDone) {
+ if (state->remaining_payload() == 0) {
+ state->listener()->OnRstStream(state->frame_header(),
+ rst_stream_fields_.error_code);
+ return DecodeStatus::kDecodeDone;
+ }
+ // Payload is too long.
+ return state->ReportFrameSizeError();
+ }
+ // 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 by the FrameDecoderState.
+ DCHECK(
+ (status == DecodeStatus::kDecodeInProgress &&
+ state->remaining_payload() > 0) ||
+ (status == DecodeStatus::kDecodeError && state->remaining_payload() == 0))
+ << "\n status=" << status
+ << "; remaining_payload=" << state->remaining_payload();
+ return status;
+}
+
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/rst_stream_payload_decoder.h b/http2/decoder/payload_decoders/rst_stream_payload_decoder.h
new file mode 100644
index 0000000..947fc06
--- /dev/null
+++ b/http2/decoder/payload_decoders/rst_stream_payload_decoder.h
@@ -0,0 +1,42 @@
+// Copyright 2016 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_RST_STREAM_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_RST_STREAM_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a RST_STREAM frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class RstStreamPayloadDecoderPeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE RstStreamPayloadDecoder {
+ public:
+ // Starts the decoding of a RST_STREAM 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 RST_STREAM frame's payload that has been split across
+ // decode buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+
+ private:
+ friend class test::RstStreamPayloadDecoderPeer;
+
+ DecodeStatus HandleStatus(FrameDecoderState* state, DecodeStatus status);
+
+ Http2RstStreamFields rst_stream_fields_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_RST_STREAM_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/rst_stream_payload_decoder_test.cc b/http2/decoder/payload_decoders/rst_stream_payload_decoder_test.cc
new file mode 100644
index 0000000..08f6621
--- /dev/null
+++ b/http2/decoder/payload_decoders/rst_stream_payload_decoder_test.cc
@@ -0,0 +1,92 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_constants_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+class RstStreamPayloadDecoderPeer {
+ public:
+ static constexpr Http2FrameType FrameType() {
+ return Http2FrameType::RST_STREAM;
+ }
+
+ // 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 OnRstStream(const Http2FrameHeader& header,
+ Http2ErrorCode error_code) override {
+ VLOG(1) << "OnRstStream: " << header << "; error_code=" << error_code;
+ StartAndEndFrame(header)->OnRstStream(header, error_code);
+ }
+
+ void OnFrameSizeError(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnFrameSizeError: " << header;
+ FrameError(header)->OnFrameSizeError(header);
+ }
+};
+
+class RstStreamPayloadDecoderTest
+ : public AbstractPayloadDecoderTest<RstStreamPayloadDecoder,
+ RstStreamPayloadDecoderPeer,
+ Listener> {
+ protected:
+ Http2RstStreamFields RandRstStreamFields() {
+ Http2RstStreamFields fields;
+ test::Randomize(&fields, RandomPtr());
+ return fields;
+ }
+};
+
+// Confirm we get an error if the payload is not the correct size to hold
+// exactly one Http2RstStreamFields.
+TEST_F(RstStreamPayloadDecoderTest, WrongSize) {
+ auto approve_size = [](size_t size) {
+ return size != Http2RstStreamFields::EncodedSize();
+ };
+ Http2FrameBuilder fb;
+ fb.Append(RandRstStreamFields());
+ fb.Append(RandRstStreamFields());
+ fb.Append(RandRstStreamFields());
+ EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size));
+}
+
+TEST_F(RstStreamPayloadDecoderTest, AllErrors) {
+ for (auto error_code : AllHttp2ErrorCodes()) {
+ Http2RstStreamFields fields{error_code};
+ Http2FrameBuilder fb;
+ fb.Append(fields);
+ Http2FrameHeader header(fb.size(), Http2FrameType::RST_STREAM, RandFlags(),
+ RandStreamId());
+ set_frame_header(header);
+ FrameParts expected(header);
+ expected.SetOptRstStreamErrorCode(error_code);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/settings_payload_decoder.cc b/http2/decoder/payload_decoders/settings_payload_decoder.cc
new file mode 100644
index 0000000..bf29c4d
--- /dev/null
+++ b/http2/decoder/payload_decoders/settings_payload_decoder.cc
@@ -0,0 +1,97 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+namespace http2 {
+
+DecodeStatus SettingsPayloadDecoder::StartDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ const Http2FrameHeader& frame_header = state->frame_header();
+ const uint32_t total_length = frame_header.payload_length;
+
+ DVLOG(2) << "SettingsPayloadDecoder::StartDecodingPayload: " << frame_header;
+ DCHECK_EQ(Http2FrameType::SETTINGS, frame_header.type);
+ DCHECK_LE(db->Remaining(), total_length);
+ DCHECK_EQ(0, frame_header.flags & ~(Http2FrameFlag::ACK));
+
+ if (frame_header.IsAck()) {
+ if (total_length == 0) {
+ state->listener()->OnSettingsAck(frame_header);
+ return DecodeStatus::kDecodeDone;
+ } else {
+ state->InitializeRemainders();
+ return state->ReportFrameSizeError();
+ }
+ } else {
+ state->InitializeRemainders();
+ state->listener()->OnSettingsStart(frame_header);
+ return StartDecodingSettings(state, db);
+ }
+}
+
+DecodeStatus SettingsPayloadDecoder::ResumeDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "SettingsPayloadDecoder::ResumeDecodingPayload"
+ << " remaining_payload=" << state->remaining_payload()
+ << " db->Remaining=" << db->Remaining();
+ DCHECK_EQ(Http2FrameType::SETTINGS, state->frame_header().type);
+ DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+
+ DecodeStatus status =
+ state->ResumeDecodingStructureInPayload(&setting_fields_, db);
+ if (status == DecodeStatus::kDecodeDone) {
+ state->listener()->OnSetting(setting_fields_);
+ return StartDecodingSettings(state, db);
+ }
+ return HandleNotDone(state, db, status);
+}
+
+DecodeStatus SettingsPayloadDecoder::StartDecodingSettings(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "SettingsPayloadDecoder::StartDecodingSettings"
+ << " remaining_payload=" << state->remaining_payload()
+ << " db->Remaining=" << db->Remaining();
+ while (state->remaining_payload() > 0) {
+ DecodeStatus status =
+ state->StartDecodingStructureInPayload(&setting_fields_, db);
+ if (status == DecodeStatus::kDecodeDone) {
+ state->listener()->OnSetting(setting_fields_);
+ continue;
+ }
+ return HandleNotDone(state, db, status);
+ }
+ DVLOG(2) << "LEAVING SettingsPayloadDecoder::StartDecodingSettings"
+ << "\n\tdb->Remaining=" << db->Remaining()
+ << "\n\t remaining_payload=" << state->remaining_payload();
+ state->listener()->OnSettingsEnd();
+ return DecodeStatus::kDecodeDone;
+}
+
+DecodeStatus SettingsPayloadDecoder::HandleNotDone(FrameDecoderState* state,
+ DecodeBuffer* db,
+ DecodeStatus status) {
+ // 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()
+ << "; db->Remaining=" << db->Remaining();
+ return status;
+}
+
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/settings_payload_decoder.h b/http2/decoder/payload_decoders/settings_payload_decoder.h
new file mode 100644
index 0000000..7e3c313
--- /dev/null
+++ b/http2/decoder/payload_decoders/settings_payload_decoder.h
@@ -0,0 +1,54 @@
+// Copyright 2016 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_SETTINGS_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_SETTINGS_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a SETTINGS frame; for the RFC, see:
+// http://httpwg.org/specs/rfc7540.html#SETTINGS
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class SettingsPayloadDecoderPeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE SettingsPayloadDecoder {
+ public:
+ // Starts the decoding of a SETTINGS 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 SETTINGS frame that has been split across decode
+ // buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+
+ private:
+ friend class test::SettingsPayloadDecoderPeer;
+
+ // Decodes as many settings as are available in the decode buffer, starting at
+ // the first byte of one setting; if a single setting is split across buffers,
+ // ResumeDecodingPayload will handle starting from where the previous call
+ // left off, and then will call StartDecodingSettings.
+ DecodeStatus StartDecodingSettings(FrameDecoderState* state,
+ DecodeBuffer* db);
+
+ // Decoding a single SETTING returned a status other than kDecodeDone; this
+ // method just brings together the DCHECKs to reduce duplication.
+ DecodeStatus HandleNotDone(FrameDecoderState* state,
+ DecodeBuffer* db,
+ DecodeStatus status);
+
+ Http2SettingFields setting_fields_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_SETTINGS_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/settings_payload_decoder_test.cc b/http2/decoder/payload_decoders/settings_payload_decoder_test.cc
new file mode 100644
index 0000000..3429779
--- /dev/null
+++ b/http2/decoder/payload_decoders/settings_payload_decoder_test.cc
@@ -0,0 +1,160 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder.h"
+
+#include <stddef.h>
+
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_constants_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+class SettingsPayloadDecoderPeer {
+ public:
+ static constexpr Http2FrameType FrameType() {
+ return Http2FrameType::SETTINGS;
+ }
+
+ // 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 Http2FrameFlag::ACK;
+ }
+};
+
+namespace {
+
+struct Listener : public FramePartsCollector {
+ void OnSettingsStart(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnSettingsStart: " << header;
+ EXPECT_EQ(Http2FrameType::SETTINGS, header.type) << header;
+ EXPECT_EQ(Http2FrameFlag(), header.flags) << header;
+ StartFrame(header)->OnSettingsStart(header);
+ }
+
+ void OnSetting(const Http2SettingFields& setting_fields) override {
+ VLOG(1) << "Http2SettingFields: setting_fields=" << setting_fields;
+ CurrentFrame()->OnSetting(setting_fields);
+ }
+
+ void OnSettingsEnd() override {
+ VLOG(1) << "OnSettingsEnd";
+ EndFrame()->OnSettingsEnd();
+ }
+
+ void OnSettingsAck(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnSettingsAck: " << header;
+ StartAndEndFrame(header)->OnSettingsAck(header);
+ }
+
+ void OnFrameSizeError(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnFrameSizeError: " << header;
+ FrameError(header)->OnFrameSizeError(header);
+ }
+};
+
+class SettingsPayloadDecoderTest
+ : public AbstractPayloadDecoderTest<SettingsPayloadDecoder,
+ SettingsPayloadDecoderPeer,
+ Listener> {
+ protected:
+ Http2SettingFields RandSettingsFields() {
+ Http2SettingFields fields;
+ test::Randomize(&fields, RandomPtr());
+ return fields;
+ }
+};
+
+// Confirm we get an error if the SETTINGS payload is not the correct size
+// to hold exactly zero or more whole Http2SettingFields.
+TEST_F(SettingsPayloadDecoderTest, SettingsWrongSize) {
+ auto approve_size = [](size_t size) {
+ // Should get an error if size is not an integral multiple of the size
+ // of one setting.
+ return 0 != (size % Http2SettingFields::EncodedSize());
+ };
+ Http2FrameBuilder fb;
+ fb.Append(RandSettingsFields());
+ fb.Append(RandSettingsFields());
+ fb.Append(RandSettingsFields());
+ EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size));
+}
+
+// Confirm we get an error if the SETTINGS ACK payload is not empty.
+TEST_F(SettingsPayloadDecoderTest, SettingsAkcWrongSize) {
+ auto approve_size = [](size_t size) { return size != 0; };
+ Http2FrameBuilder fb;
+ fb.Append(RandSettingsFields());
+ fb.Append(RandSettingsFields());
+ fb.Append(RandSettingsFields());
+ EXPECT_TRUE(VerifyDetectsFrameSizeError(Http2FrameFlag::ACK, fb.buffer(),
+ approve_size));
+}
+
+// SETTINGS must have stream_id==0, but the payload decoder doesn't check that.
+TEST_F(SettingsPayloadDecoderTest, SettingsAck) {
+ for (int stream_id = 0; stream_id < 3; ++stream_id) {
+ Http2FrameHeader header(0, Http2FrameType::SETTINGS,
+ RandFlags() | Http2FrameFlag::ACK, stream_id);
+ set_frame_header(header);
+ FrameParts expected(header);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays("", expected));
+ }
+}
+
+// Try several values of each known SETTINGS parameter.
+TEST_F(SettingsPayloadDecoderTest, OneRealSetting) {
+ std::vector<uint32_t> values = {0, 1, 0xffffffff, Random().Rand32()};
+ for (auto param : AllHttp2SettingsParameters()) {
+ for (uint32_t value : values) {
+ Http2SettingFields fields(param, value);
+ Http2FrameBuilder fb;
+ fb.Append(fields);
+ Http2FrameHeader header(fb.size(), Http2FrameType::SETTINGS, RandFlags(),
+ RandStreamId());
+ set_frame_header(header);
+ FrameParts expected(header);
+ expected.AppendSetting(fields);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+ }
+ }
+}
+
+// Decode a SETTINGS frame with lots of fields.
+TEST_F(SettingsPayloadDecoderTest, ManySettings) {
+ const size_t num_settings = 100;
+ const size_t size = Http2SettingFields::EncodedSize() * num_settings;
+ Http2FrameHeader header(size, Http2FrameType::SETTINGS,
+ RandFlags(), // & ~Http2FrameFlag::ACK,
+ RandStreamId());
+ set_frame_header(header);
+ FrameParts expected(header);
+ Http2FrameBuilder fb;
+ for (size_t n = 0; n < num_settings; ++n) {
+ Http2SettingFields fields(static_cast<Http2SettingsParameter>(n),
+ Random().Rand32());
+ fb.Append(fields);
+ expected.AppendSetting(fields);
+ }
+ ASSERT_EQ(size, fb.size());
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/unknown_payload_decoder.cc b/http2/decoder/payload_decoders/unknown_payload_decoder.cc
new file mode 100644
index 0000000..10eaf24
--- /dev/null
+++ b/http2/decoder/payload_decoders/unknown_payload_decoder.cc
@@ -0,0 +1,55 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+namespace http2 {
+
+DecodeStatus UnknownPayloadDecoder::StartDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ const Http2FrameHeader& frame_header = state->frame_header();
+
+ DVLOG(2) << "UnknownPayloadDecoder::StartDecodingPayload: " << frame_header;
+ DCHECK(!IsSupportedHttp2FrameType(frame_header.type)) << frame_header;
+ DCHECK_LE(db->Remaining(), frame_header.payload_length);
+
+ state->InitializeRemainders();
+ state->listener()->OnUnknownStart(frame_header);
+ return ResumeDecodingPayload(state, db);
+}
+
+DecodeStatus UnknownPayloadDecoder::ResumeDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "UnknownPayloadDecoder::ResumeDecodingPayload "
+ << "remaining_payload=" << state->remaining_payload()
+ << "; db->Remaining=" << db->Remaining();
+ DCHECK(!IsSupportedHttp2FrameType(state->frame_header().type))
+ << state->frame_header();
+ DCHECK_LE(state->remaining_payload(), state->frame_header().payload_length);
+ DCHECK_LE(db->Remaining(), state->remaining_payload());
+
+ size_t avail = db->Remaining();
+ if (avail > 0) {
+ state->listener()->OnUnknownPayload(db->cursor(), avail);
+ db->AdvanceCursor(avail);
+ state->ConsumePayload(avail);
+ }
+ if (state->remaining_payload() == 0) {
+ state->listener()->OnUnknownEnd();
+ return DecodeStatus::kDecodeDone;
+ }
+ return DecodeStatus::kDecodeInProgress;
+}
+
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/unknown_payload_decoder.h b/http2/decoder/payload_decoders/unknown_payload_decoder.h
new file mode 100644
index 0000000..7bf4103
--- /dev/null
+++ b/http2/decoder/payload_decoders/unknown_payload_decoder.h
@@ -0,0 +1,33 @@
+// Copyright 2016 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_UNKNOWN_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_UNKNOWN_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a frame whose type unknown. According to the HTTP/2
+// specification (http://httpwg.org/specs/rfc7540.html#FrameHeader):
+// Implementations MUST ignore and discard any frame that has
+// a type that is unknown.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE UnknownPayloadDecoder {
+ public:
+ // Starts decoding a payload of unknown type; just passes it to the listener.
+ DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+ // Resumes decoding a payload of unknown type that has been split across
+ // decode buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_UNKNOWN_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/unknown_payload_decoder_test.cc b/http2/decoder/payload_decoders/unknown_payload_decoder_test.cc
new file mode 100644
index 0000000..7ba95f7
--- /dev/null
+++ b/http2/decoder/payload_decoders/unknown_payload_decoder_test.cc
@@ -0,0 +1,100 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder.h"
+
+#include <stddef.h>
+
+#include <type_traits>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+namespace {
+Http2FrameType g_unknown_frame_type;
+} // namespace
+
+// Provides friend access to an instance of the payload decoder, and also
+// provides info to aid in testing.
+class UnknownPayloadDecoderPeer {
+ public:
+ static Http2FrameType FrameType() { return g_unknown_frame_type; }
+
+ // 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 OnUnknownStart(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnUnknownStart: " << header;
+ StartFrame(header)->OnUnknownStart(header);
+ }
+
+ void OnUnknownPayload(const char* data, size_t len) override {
+ VLOG(1) << "OnUnknownPayload: len=" << len;
+ CurrentFrame()->OnUnknownPayload(data, len);
+ }
+
+ void OnUnknownEnd() override {
+ VLOG(1) << "OnUnknownEnd";
+ EndFrame()->OnUnknownEnd();
+ }
+};
+
+constexpr bool SupportedFrameType = false;
+
+class UnknownPayloadDecoderTest
+ : public AbstractPayloadDecoderTest<UnknownPayloadDecoder,
+ UnknownPayloadDecoderPeer,
+ Listener,
+ SupportedFrameType>,
+ public ::testing::WithParamInterface<uint32_t> {
+ protected:
+ UnknownPayloadDecoderTest() : length_(GetParam()) {
+ VLOG(1) << "################ length_=" << length_ << " ################";
+
+ // Each test case will choose a random frame type that isn't supported.
+ do {
+ g_unknown_frame_type = static_cast<Http2FrameType>(Random().Rand8());
+ } while (IsSupportedHttp2FrameType(g_unknown_frame_type));
+ }
+
+ const uint32_t length_;
+};
+
+INSTANTIATE_TEST_CASE_P(VariousLengths,
+ UnknownPayloadDecoderTest,
+ ::testing::Values(0, 1, 2, 3, 255, 256));
+
+TEST_P(UnknownPayloadDecoderTest, ValidLength) {
+ Http2String unknown_payload = Random().RandString(length_);
+ Http2FrameHeader frame_header(length_, g_unknown_frame_type, Random().Rand8(),
+ RandStreamId());
+ set_frame_header(frame_header);
+ FrameParts expected(frame_header, unknown_payload);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(unknown_payload, expected));
+ // TODO(jamessynge): Check here (and in other such tests) that the fast
+ // and slow decode counts are both non-zero. Perhaps also add some kind of
+ // test for the listener having been called. That could simply be a test
+ // that there is a single collected FrameParts instance, and that it matches
+ // expected.
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/window_update_payload_decoder.cc b/http2/decoder/payload_decoders/window_update_payload_decoder.cc
new file mode 100644
index 0000000..c0bb028
--- /dev/null
+++ b/http2/decoder/payload_decoders/window_update_payload_decoder.cc
@@ -0,0 +1,82 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_http2_structures.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+namespace http2 {
+
+DecodeStatus WindowUpdatePayloadDecoder::StartDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ const Http2FrameHeader& frame_header = state->frame_header();
+ const uint32_t total_length = frame_header.payload_length;
+
+ DVLOG(2) << "WindowUpdatePayloadDecoder::StartDecodingPayload: "
+ << frame_header;
+
+ DCHECK_EQ(Http2FrameType::WINDOW_UPDATE, frame_header.type);
+ DCHECK_LE(db->Remaining(), total_length);
+
+ // WINDOW_UPDATE frames have no flags.
+ DCHECK_EQ(0, frame_header.flags);
+
+ // Special case for when the payload is the correct size and entirely in
+ // the buffer.
+ if (db->Remaining() == Http2WindowUpdateFields::EncodedSize() &&
+ total_length == Http2WindowUpdateFields::EncodedSize()) {
+ DoDecode(&window_update_fields_, db);
+ state->listener()->OnWindowUpdate(
+ frame_header, window_update_fields_.window_size_increment);
+ return DecodeStatus::kDecodeDone;
+ }
+ state->InitializeRemainders();
+ return HandleStatus(state, state->StartDecodingStructureInPayload(
+ &window_update_fields_, db));
+}
+
+DecodeStatus WindowUpdatePayloadDecoder::ResumeDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "ResumeDecodingPayload: remaining_payload="
+ << state->remaining_payload()
+ << "; db->Remaining=" << db->Remaining();
+ DCHECK_EQ(Http2FrameType::WINDOW_UPDATE, state->frame_header().type);
+ DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+ return HandleStatus(state, state->ResumeDecodingStructureInPayload(
+ &window_update_fields_, db));
+}
+
+DecodeStatus WindowUpdatePayloadDecoder::HandleStatus(FrameDecoderState* state,
+ DecodeStatus status) {
+ DVLOG(2) << "HandleStatus: status=" << status
+ << "; remaining_payload=" << state->remaining_payload();
+ if (status == DecodeStatus::kDecodeDone) {
+ if (state->remaining_payload() == 0) {
+ state->listener()->OnWindowUpdate(
+ state->frame_header(), window_update_fields_.window_size_increment);
+ return DecodeStatus::kDecodeDone;
+ }
+ // Payload is too long.
+ return state->ReportFrameSizeError();
+ }
+ // 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();
+ return status;
+}
+
+} // namespace http2
diff --git a/http2/decoder/payload_decoders/window_update_payload_decoder.h b/http2/decoder/payload_decoders/window_update_payload_decoder.h
new file mode 100644
index 0000000..158c165
--- /dev/null
+++ b/http2/decoder/payload_decoders/window_update_payload_decoder.h
@@ -0,0 +1,42 @@
+// Copyright 2016 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_WINDOW_UPDATE_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_WINDOW_UPDATE_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a WINDOW_UPDATE frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class WindowUpdatePayloadDecoderPeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE WindowUpdatePayloadDecoder {
+ public:
+ // Starts decoding a WINDOW_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 WINDOW_UPDATE frame's payload that has been split across
+ // decode buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+
+ private:
+ friend class test::WindowUpdatePayloadDecoderPeer;
+
+ DecodeStatus HandleStatus(FrameDecoderState* state, DecodeStatus status);
+
+ Http2WindowUpdateFields window_update_fields_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_WINDOW_UPDATE_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/window_update_payload_decoder_test.cc b/http2/decoder/payload_decoders/window_update_payload_decoder_test.cc
new file mode 100644
index 0000000..77a2b90
--- /dev/null
+++ b/http2/decoder/payload_decoders/window_update_payload_decoder_test.cc
@@ -0,0 +1,95 @@
+// Copyright 2016 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 "net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+class WindowUpdatePayloadDecoderPeer {
+ public:
+ static constexpr Http2FrameType FrameType() {
+ return Http2FrameType::WINDOW_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 OnWindowUpdate(const Http2FrameHeader& header,
+ uint32_t window_size_increment) override {
+ VLOG(1) << "OnWindowUpdate: " << header
+ << "; window_size_increment=" << window_size_increment;
+ EXPECT_EQ(Http2FrameType::WINDOW_UPDATE, header.type);
+ StartAndEndFrame(header)->OnWindowUpdate(header, window_size_increment);
+ }
+
+ void OnFrameSizeError(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnFrameSizeError: " << header;
+ FrameError(header)->OnFrameSizeError(header);
+ }
+};
+
+class WindowUpdatePayloadDecoderTest
+ : public AbstractPayloadDecoderTest<WindowUpdatePayloadDecoder,
+ WindowUpdatePayloadDecoderPeer,
+ Listener> {
+ protected:
+ Http2WindowUpdateFields RandWindowUpdateFields() {
+ Http2WindowUpdateFields fields;
+ test::Randomize(&fields, RandomPtr());
+ VLOG(3) << "RandWindowUpdateFields: " << fields;
+ return fields;
+ }
+};
+
+// Confirm we get an error if the payload is not the correct size to hold
+// exactly one Http2WindowUpdateFields.
+TEST_F(WindowUpdatePayloadDecoderTest, WrongSize) {
+ auto approve_size = [](size_t size) {
+ return size != Http2WindowUpdateFields::EncodedSize();
+ };
+ Http2FrameBuilder fb;
+ fb.Append(RandWindowUpdateFields());
+ fb.Append(RandWindowUpdateFields());
+ fb.Append(RandWindowUpdateFields());
+ EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size));
+}
+
+TEST_F(WindowUpdatePayloadDecoderTest, VariousPayloads) {
+ for (int n = 0; n < 100; ++n) {
+ uint32_t stream_id = n == 0 ? 0 : RandStreamId();
+ Http2WindowUpdateFields fields = RandWindowUpdateFields();
+ Http2FrameBuilder fb;
+ fb.Append(fields);
+ Http2FrameHeader header(fb.size(), Http2FrameType::WINDOW_UPDATE,
+ RandFlags(), stream_id);
+ set_frame_header(header);
+ FrameParts expected(header);
+ expected.SetOptWindowUpdateIncrement(fields.window_size_increment);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace http2