Move all of HTTP/2 test tools into http2/test_tools.
PiperOrigin-RevId: 446965334
diff --git a/quiche/http2/test_tools/frame_decoder_state_test_util.cc b/quiche/http2/test_tools/frame_decoder_state_test_util.cc
new file mode 100644
index 0000000..e3eab59
--- /dev/null
+++ b/quiche/http2/test_tools/frame_decoder_state_test_util.cc
@@ -0,0 +1,34 @@
+// 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 "quiche/http2/test_tools/frame_decoder_state_test_util.h"
+
+#include "quiche/http2/http2_structures.h"
+#include "quiche/http2/test_tools/http2_random.h"
+#include "quiche/http2/test_tools/http2_structure_decoder_test_util.h"
+#include "quiche/http2/test_tools/http2_structures_test_util.h"
+#include "quiche/http2/test_tools/random_decoder_test.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+
+namespace http2 {
+namespace test {
+
+// static
+void FrameDecoderStatePeer::Randomize(FrameDecoderState* p, Http2Random* rng) {
+ QUICHE_VLOG(1) << "FrameDecoderStatePeer::Randomize";
+ ::http2::test::Randomize(&p->frame_header_, rng);
+ p->remaining_payload_ = rng->Rand32();
+ p->remaining_padding_ = rng->Rand32();
+ Http2StructureDecoderPeer::Randomize(&p->structure_decoder_, rng);
+}
+
+// static
+void FrameDecoderStatePeer::set_frame_header(const Http2FrameHeader& header,
+ FrameDecoderState* p) {
+ QUICHE_VLOG(1) << "FrameDecoderStatePeer::set_frame_header " << header;
+ p->frame_header_ = header;
+}
+
+} // namespace test
+} // namespace http2
diff --git a/quiche/http2/test_tools/frame_decoder_state_test_util.h b/quiche/http2/test_tools/frame_decoder_state_test_util.h
new file mode 100644
index 0000000..6047923
--- /dev/null
+++ b/quiche/http2/test_tools/frame_decoder_state_test_util.h
@@ -0,0 +1,37 @@
+// 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_TEST_TOOLS_FRAME_DECODER_STATE_TEST_UTIL_H_
+#define QUICHE_HTTP2_TEST_TOOLS_FRAME_DECODER_STATE_TEST_UTIL_H_
+
+#include "quiche/http2/decoder/frame_decoder_state.h"
+#include "quiche/http2/http2_structures.h"
+#include "quiche/http2/test_tools/random_decoder_test.h"
+#include "quiche/common/platform/api/quiche_export.h"
+
+namespace http2 {
+namespace test {
+
+class QUICHE_NO_EXPORT FrameDecoderStatePeer {
+ public:
+ // Randomizes (i.e. corrupts) the fields of the FrameDecoderState.
+ // PayloadDecoderBaseTest::StartDecoding calls this before passing the first
+ // decode buffer to the payload decoder, which increases the likelihood of
+ // detecting any use of prior states of the decoder on the decoding of
+ // future payloads.
+ static void Randomize(FrameDecoderState* p, Http2Random* rng);
+
+ // Inject a frame header into the FrameDecoderState.
+ // PayloadDecoderBaseTest::StartDecoding calls this just after calling
+ // Randomize (above), to simulate a full frame decoder having just finished
+ // decoding the common frame header and then calling the appropriate payload
+ // decoder based on the frame type in that frame header.
+ static void set_frame_header(const Http2FrameHeader& header,
+ FrameDecoderState* p);
+};
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_TEST_TOOLS_FRAME_DECODER_STATE_TEST_UTIL_H_
diff --git a/quiche/http2/test_tools/frame_parts.cc b/quiche/http2/test_tools/frame_parts.cc
index ba0396a..3897238 100644
--- a/quiche/http2/test_tools/frame_parts.cc
+++ b/quiche/http2/test_tools/frame_parts.cc
@@ -7,7 +7,7 @@
#include <type_traits>
#include "absl/strings/escaping.h"
-#include "quiche/http2/http2_structures_test_util.h"
+#include "quiche/http2/test_tools/http2_structures_test_util.h"
#include "quiche/common/platform/api/quiche_logging.h"
#include "quiche/common/platform/api/quiche_test.h"
#include "quiche/common/platform/api/quiche_test_helpers.h"
diff --git a/quiche/http2/test_tools/frame_parts_collector.cc b/quiche/http2/test_tools/frame_parts_collector.cc
index f9de389..2b8f616 100644
--- a/quiche/http2/test_tools/frame_parts_collector.cc
+++ b/quiche/http2/test_tools/frame_parts_collector.cc
@@ -6,7 +6,7 @@
#include <utility>
-#include "quiche/http2/http2_structures_test_util.h"
+#include "quiche/http2/test_tools/http2_structures_test_util.h"
#include "quiche/common/platform/api/quiche_logging.h"
#include "quiche/common/platform/api/quiche_test.h"
diff --git a/quiche/http2/test_tools/frame_parts_collector.h b/quiche/http2/test_tools/frame_parts_collector.h
index ccb6142..58adcdb 100644
--- a/quiche/http2/test_tools/frame_parts_collector.h
+++ b/quiche/http2/test_tools/frame_parts_collector.h
@@ -14,9 +14,9 @@
#include <vector>
#include "quiche/http2/decoder/http2_frame_decoder_listener.h"
-#include "quiche/http2/decoder/http2_frame_decoder_listener_test_util.h"
#include "quiche/http2/http2_structures.h"
#include "quiche/http2/test_tools/frame_parts.h"
+#include "quiche/http2/test_tools/http2_frame_decoder_listener_test_util.h"
#include "quiche/common/platform/api/quiche_export.h"
namespace http2 {
diff --git a/quiche/http2/test_tools/hpack_block_builder.cc b/quiche/http2/test_tools/hpack_block_builder.cc
new file mode 100644
index 0000000..bd9119a
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_block_builder.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 "quiche/http2/test_tools/hpack_block_builder.h"
+
+#include "quiche/http2/hpack/varint/hpack_varint_encoder.h"
+#include "quiche/common/platform/api/quiche_bug_tracker.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace test {
+
+void HpackBlockBuilder::AppendHighBitsAndVarint(uint8_t high_bits,
+ uint8_t prefix_length,
+ uint64_t varint) {
+ EXPECT_LE(3, prefix_length);
+ EXPECT_LE(prefix_length, 8);
+
+ HpackVarintEncoder::Encode(high_bits, prefix_length, varint, &buffer_);
+}
+
+void HpackBlockBuilder::AppendEntryTypeAndVarint(HpackEntryType entry_type,
+ uint64_t varint) {
+ uint8_t high_bits;
+ uint8_t prefix_length; // Bits of the varint prefix in the first byte.
+ switch (entry_type) {
+ case HpackEntryType::kIndexedHeader:
+ high_bits = 0x80;
+ prefix_length = 7;
+ break;
+ case HpackEntryType::kDynamicTableSizeUpdate:
+ high_bits = 0x20;
+ prefix_length = 5;
+ break;
+ case HpackEntryType::kIndexedLiteralHeader:
+ high_bits = 0x40;
+ prefix_length = 6;
+ break;
+ case HpackEntryType::kUnindexedLiteralHeader:
+ high_bits = 0x00;
+ prefix_length = 4;
+ break;
+ case HpackEntryType::kNeverIndexedLiteralHeader:
+ high_bits = 0x10;
+ prefix_length = 4;
+ break;
+ default:
+ QUICHE_BUG(http2_bug_110_1) << "Unreached, entry_type=" << entry_type;
+ high_bits = 0;
+ prefix_length = 0;
+ break;
+ }
+ AppendHighBitsAndVarint(high_bits, prefix_length, varint);
+}
+
+void HpackBlockBuilder::AppendString(bool is_huffman_encoded,
+ absl::string_view str) {
+ uint8_t high_bits = is_huffman_encoded ? 0x80 : 0;
+ uint8_t prefix_length = 7;
+ AppendHighBitsAndVarint(high_bits, prefix_length, str.size());
+ buffer_.append(str.data(), str.size());
+}
+
+} // namespace test
+} // namespace http2
diff --git a/quiche/http2/test_tools/hpack_block_builder.h b/quiche/http2/test_tools/hpack_block_builder.h
new file mode 100644
index 0000000..5aec637
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_block_builder.h
@@ -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.
+
+#ifndef QUICHE_HTTP2_TEST_TOOLS_HPACK_BLOCK_BUILDER_H_
+#define QUICHE_HTTP2_TEST_TOOLS_HPACK_BLOCK_BUILDER_H_
+
+// HpackBlockBuilder builds wire-format HPACK blocks (or fragments thereof)
+// from components.
+
+// Supports very large varints to enable tests to create HPACK blocks with
+// values that the decoder should reject. For now, this is only intended for
+// use in tests, and thus has EXPECT* in the code. If desired to use it in an
+// encoder, it will need optimization work, especially w.r.t memory mgmt, and
+// the EXPECT* will need to be removed or replaced with QUICHE_DCHECKs. And of
+// course the support for very large varints will not be needed in production
+// code.
+
+#include <stddef.h>
+
+#include <cstdint>
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "quiche/http2/hpack/http2_hpack_constants.h"
+#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace test {
+
+class QUICHE_NO_EXPORT HpackBlockBuilder {
+ public:
+ explicit HpackBlockBuilder(absl::string_view initial_contents)
+ : buffer_(initial_contents.data(), initial_contents.size()) {}
+ HpackBlockBuilder() {}
+ ~HpackBlockBuilder() {}
+
+ size_t size() const { return buffer_.size(); }
+ const std::string& buffer() const { return buffer_; }
+
+ //----------------------------------------------------------------------------
+ // Methods for appending a valid HPACK entry.
+
+ void AppendIndexedHeader(uint64_t index) {
+ AppendEntryTypeAndVarint(HpackEntryType::kIndexedHeader, index);
+ }
+
+ void AppendDynamicTableSizeUpdate(uint64_t size) {
+ AppendEntryTypeAndVarint(HpackEntryType::kDynamicTableSizeUpdate, size);
+ }
+
+ void AppendNameIndexAndLiteralValue(HpackEntryType entry_type,
+ uint64_t name_index,
+ bool value_is_huffman_encoded,
+ absl::string_view value) {
+ // name_index==0 would indicate that the entry includes a literal name.
+ // Call AppendLiteralNameAndValue in that case.
+ EXPECT_NE(0u, name_index);
+ AppendEntryTypeAndVarint(entry_type, name_index);
+ AppendString(value_is_huffman_encoded, value);
+ }
+
+ void AppendLiteralNameAndValue(HpackEntryType entry_type,
+ bool name_is_huffman_encoded,
+ absl::string_view name,
+ bool value_is_huffman_encoded,
+ absl::string_view value) {
+ AppendEntryTypeAndVarint(entry_type, 0);
+ AppendString(name_is_huffman_encoded, name);
+ AppendString(value_is_huffman_encoded, value);
+ }
+
+ //----------------------------------------------------------------------------
+ // Primitive methods that are not guaranteed to write a valid HPACK entry.
+
+ // Appends a varint, with the specified high_bits above the prefix of the
+ // varint.
+ void AppendHighBitsAndVarint(uint8_t high_bits, uint8_t prefix_length,
+ uint64_t varint);
+
+ // Append the start of an HPACK entry for the specified type, with the
+ // specified varint.
+ void AppendEntryTypeAndVarint(HpackEntryType entry_type, uint64_t varint);
+
+ // Append a header string (i.e. a header name or value) in HPACK format.
+ // Does NOT perform Huffman encoding.
+ void AppendString(bool is_huffman_encoded, absl::string_view str);
+
+ private:
+ std::string buffer_;
+};
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_TEST_TOOLS_HPACK_BLOCK_BUILDER_H_
diff --git a/quiche/http2/test_tools/hpack_block_builder_test.cc b/quiche/http2/test_tools/hpack_block_builder_test.cc
new file mode 100644
index 0000000..7642049
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_block_builder_test.cc
@@ -0,0 +1,169 @@
+// 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 "quiche/http2/test_tools/hpack_block_builder.h"
+
+#include "absl/strings/escaping.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace test {
+namespace {
+const bool kUncompressed = false;
+const bool kCompressed = true;
+
+// TODO(jamessynge): Once static table code is checked in, switch to using
+// constants from there.
+const uint32_t kStaticTableMethodGET = 2;
+const uint32_t kStaticTablePathSlash = 4;
+const uint32_t kStaticTableSchemeHttp = 6;
+
+// Tests of encoding per the RFC. See:
+// http://httpwg.org/specs/rfc7541.html#header.field.representation.examples
+// The expected values have been copied from the RFC.
+TEST(HpackBlockBuilderTest, ExamplesFromSpecC2) {
+ {
+ HpackBlockBuilder b;
+ b.AppendLiteralNameAndValue(HpackEntryType::kIndexedLiteralHeader,
+ kUncompressed, "custom-key", kUncompressed,
+ "custom-header");
+ EXPECT_EQ(26u, b.size());
+
+ const char kExpected[] =
+ "\x40" // == Literal indexed ==
+ "\x0a" // Name length (10)
+ "custom-key" // Name
+ "\x0d" // Value length (13)
+ "custom-header"; // Value
+ EXPECT_EQ(kExpected, b.buffer());
+ }
+ {
+ HpackBlockBuilder b;
+ b.AppendNameIndexAndLiteralValue(HpackEntryType::kUnindexedLiteralHeader, 4,
+ kUncompressed, "/sample/path");
+ EXPECT_EQ(14u, b.size());
+
+ const char kExpected[] =
+ "\x04" // == Literal unindexed, name index 0x04 ==
+ "\x0c" // Value length (12)
+ "/sample/path"; // Value
+ EXPECT_EQ(kExpected, b.buffer());
+ }
+ {
+ HpackBlockBuilder b;
+ b.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader,
+ kUncompressed, "password", kUncompressed,
+ "secret");
+ EXPECT_EQ(17u, b.size());
+
+ const char kExpected[] =
+ "\x10" // == Literal never indexed ==
+ "\x08" // Name length (8)
+ "password" // Name
+ "\x06" // Value length (6)
+ "secret"; // Value
+ EXPECT_EQ(kExpected, b.buffer());
+ }
+ {
+ HpackBlockBuilder b;
+ b.AppendIndexedHeader(2);
+ EXPECT_EQ(1u, b.size());
+
+ const char kExpected[] = "\x82"; // == Indexed (2) ==
+ EXPECT_EQ(kExpected, b.buffer());
+ }
+}
+
+// Tests of encoding per the RFC. See:
+// http://httpwg.org/specs/rfc7541.html#request.examples.without.huffman.coding
+TEST(HpackBlockBuilderTest, ExamplesFromSpecC3) {
+ {
+ // Header block to encode:
+ // :method: GET
+ // :scheme: http
+ // :path: /
+ // :authority: www.example.com
+ HpackBlockBuilder b;
+ b.AppendIndexedHeader(2); // :method: GET
+ b.AppendIndexedHeader(6); // :scheme: http
+ b.AppendIndexedHeader(4); // :path: /
+ b.AppendNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, 1,
+ kUncompressed, "www.example.com");
+ EXPECT_EQ(20u, b.size());
+
+ // Hex dump of encoded data (copied from RFC):
+ // 0x0000: 8286 8441 0f77 7777 2e65 7861 6d70 6c65 ...A.www.example
+ // 0x0010: 2e63 6f6d .com
+
+ const std::string expected =
+ absl::HexStringToBytes("828684410f7777772e6578616d706c652e636f6d");
+ EXPECT_EQ(expected, b.buffer());
+ }
+}
+
+// Tests of encoding per the RFC. See:
+// http://httpwg.org/specs/rfc7541.html#request.examples.with.huffman.coding
+TEST(HpackBlockBuilderTest, ExamplesFromSpecC4) {
+ {
+ // Header block to encode:
+ // :method: GET
+ // :scheme: http
+ // :path: /
+ // :authority: www.example.com (Huffman encoded)
+ HpackBlockBuilder b;
+ b.AppendIndexedHeader(kStaticTableMethodGET);
+ b.AppendIndexedHeader(kStaticTableSchemeHttp);
+ b.AppendIndexedHeader(kStaticTablePathSlash);
+ const char kHuffmanWwwExampleCom[] = {'\xf1', '\xe3', '\xc2', '\xe5',
+ '\xf2', '\x3a', '\x6b', '\xa0',
+ '\xab', '\x90', '\xf4', '\xff'};
+ b.AppendNameIndexAndLiteralValue(
+ HpackEntryType::kIndexedLiteralHeader, 1, kCompressed,
+ absl::string_view(kHuffmanWwwExampleCom, sizeof kHuffmanWwwExampleCom));
+ EXPECT_EQ(17u, b.size());
+
+ // Hex dump of encoded data (copied from RFC):
+ // 0x0000: 8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4 ...A......:k....
+ // 0x0010: ff .
+
+ const std::string expected =
+ absl::HexStringToBytes("828684418cf1e3c2e5f23a6ba0ab90f4ff");
+ EXPECT_EQ(expected, b.buffer());
+ }
+}
+
+TEST(HpackBlockBuilderTest, DynamicTableSizeUpdate) {
+ {
+ HpackBlockBuilder b;
+ b.AppendDynamicTableSizeUpdate(0);
+ EXPECT_EQ(1u, b.size());
+
+ const char kData[] = {'\x20'};
+ absl::string_view expected(kData, sizeof kData);
+ EXPECT_EQ(expected, b.buffer());
+ }
+ {
+ HpackBlockBuilder b;
+ b.AppendDynamicTableSizeUpdate(4096); // The default size.
+ EXPECT_EQ(3u, b.size());
+
+ const char kData[] = {'\x3f', '\xe1', '\x1f'};
+ absl::string_view expected(kData, sizeof kData);
+ EXPECT_EQ(expected, b.buffer());
+ }
+ {
+ HpackBlockBuilder b;
+ b.AppendDynamicTableSizeUpdate(1000000000000); // A very large value.
+ EXPECT_EQ(7u, b.size());
+
+ const char kData[] = {'\x3f', '\xe1', '\x9f', '\x94',
+ '\xa5', '\x8d', '\x1d'};
+ absl::string_view expected(kData, sizeof kData);
+ EXPECT_EQ(expected, b.buffer());
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/quiche/http2/test_tools/hpack_block_collector.cc b/quiche/http2/test_tools/hpack_block_collector.cc
new file mode 100644
index 0000000..7cd01ac
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_block_collector.cc
@@ -0,0 +1,143 @@
+// 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 "quiche/http2/test_tools/hpack_block_collector.h"
+
+#include <algorithm>
+#include <memory>
+
+#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/platform/api/quiche_test_helpers.h"
+
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+
+namespace http2 {
+namespace test {
+
+HpackBlockCollector::HpackBlockCollector() = default;
+HpackBlockCollector::HpackBlockCollector(const HpackBlockCollector& other)
+ : pending_entry_(other.pending_entry_), entries_(other.entries_) {}
+HpackBlockCollector::~HpackBlockCollector() = default;
+
+void HpackBlockCollector::OnIndexedHeader(size_t index) {
+ pending_entry_.OnIndexedHeader(index);
+ PushPendingEntry();
+}
+void HpackBlockCollector::OnDynamicTableSizeUpdate(size_t size) {
+ pending_entry_.OnDynamicTableSizeUpdate(size);
+ PushPendingEntry();
+}
+void HpackBlockCollector::OnStartLiteralHeader(HpackEntryType header_type,
+ size_t maybe_name_index) {
+ pending_entry_.OnStartLiteralHeader(header_type, maybe_name_index);
+}
+void HpackBlockCollector::OnNameStart(bool huffman_encoded, size_t len) {
+ pending_entry_.OnNameStart(huffman_encoded, len);
+}
+void HpackBlockCollector::OnNameData(const char* data, size_t len) {
+ pending_entry_.OnNameData(data, len);
+}
+void HpackBlockCollector::OnNameEnd() { pending_entry_.OnNameEnd(); }
+void HpackBlockCollector::OnValueStart(bool huffman_encoded, size_t len) {
+ pending_entry_.OnValueStart(huffman_encoded, len);
+}
+void HpackBlockCollector::OnValueData(const char* data, size_t len) {
+ pending_entry_.OnValueData(data, len);
+}
+void HpackBlockCollector::OnValueEnd() {
+ pending_entry_.OnValueEnd();
+ PushPendingEntry();
+}
+
+void HpackBlockCollector::PushPendingEntry() {
+ EXPECT_TRUE(pending_entry_.IsComplete());
+ QUICHE_DVLOG(2) << "PushPendingEntry: " << pending_entry_;
+ entries_.push_back(pending_entry_);
+ EXPECT_TRUE(entries_.back().IsComplete());
+ pending_entry_.Clear();
+}
+void HpackBlockCollector::Clear() {
+ pending_entry_.Clear();
+ entries_.clear();
+}
+
+void HpackBlockCollector::ExpectIndexedHeader(size_t index) {
+ entries_.push_back(
+ HpackEntryCollector(HpackEntryType::kIndexedHeader, index));
+}
+void HpackBlockCollector::ExpectDynamicTableSizeUpdate(size_t size) {
+ entries_.push_back(
+ HpackEntryCollector(HpackEntryType::kDynamicTableSizeUpdate, size));
+}
+void HpackBlockCollector::ExpectNameIndexAndLiteralValue(
+ HpackEntryType type, size_t index, bool value_huffman,
+ const std::string& value) {
+ entries_.push_back(HpackEntryCollector(type, index, value_huffman, value));
+}
+void HpackBlockCollector::ExpectLiteralNameAndValue(HpackEntryType type,
+ bool name_huffman,
+ const std::string& name,
+ bool value_huffman,
+ const std::string& value) {
+ entries_.push_back(
+ HpackEntryCollector(type, name_huffman, name, value_huffman, value));
+}
+
+void HpackBlockCollector::ShuffleEntries(Http2Random* rng) {
+ std::shuffle(entries_.begin(), entries_.end(), *rng);
+}
+
+void HpackBlockCollector::AppendToHpackBlockBuilder(
+ HpackBlockBuilder* hbb) const {
+ QUICHE_CHECK(IsNotPending());
+ for (const auto& entry : entries_) {
+ entry.AppendToHpackBlockBuilder(hbb);
+ }
+}
+
+AssertionResult HpackBlockCollector::ValidateSoleIndexedHeader(
+ size_t ndx) const {
+ VERIFY_TRUE(pending_entry_.IsClear());
+ VERIFY_EQ(1u, entries_.size());
+ VERIFY_TRUE(entries_.front().ValidateIndexedHeader(ndx));
+ return AssertionSuccess();
+}
+AssertionResult HpackBlockCollector::ValidateSoleLiteralValueHeader(
+ HpackEntryType expected_type, size_t expected_index,
+ bool expected_value_huffman, absl::string_view expected_value) const {
+ VERIFY_TRUE(pending_entry_.IsClear());
+ VERIFY_EQ(1u, entries_.size());
+ VERIFY_TRUE(entries_.front().ValidateLiteralValueHeader(
+ expected_type, expected_index, expected_value_huffman, expected_value));
+ return AssertionSuccess();
+}
+AssertionResult HpackBlockCollector::ValidateSoleLiteralNameValueHeader(
+ HpackEntryType expected_type, bool expected_name_huffman,
+ absl::string_view expected_name, bool expected_value_huffman,
+ absl::string_view expected_value) const {
+ VERIFY_TRUE(pending_entry_.IsClear());
+ VERIFY_EQ(1u, entries_.size());
+ VERIFY_TRUE(entries_.front().ValidateLiteralNameValueHeader(
+ expected_type, expected_name_huffman, expected_name,
+ expected_value_huffman, expected_value));
+ return AssertionSuccess();
+}
+AssertionResult HpackBlockCollector::ValidateSoleDynamicTableSizeUpdate(
+ size_t size) const {
+ VERIFY_TRUE(pending_entry_.IsClear());
+ VERIFY_EQ(1u, entries_.size());
+ VERIFY_TRUE(entries_.front().ValidateDynamicTableSizeUpdate(size));
+ return AssertionSuccess();
+}
+
+AssertionResult HpackBlockCollector::VerifyEq(
+ const HpackBlockCollector& that) const {
+ VERIFY_EQ(pending_entry_, that.pending_entry_);
+ VERIFY_EQ(entries_, that.entries_);
+ return AssertionSuccess();
+}
+
+} // namespace test
+} // namespace http2
diff --git a/quiche/http2/test_tools/hpack_block_collector.h b/quiche/http2/test_tools/hpack_block_collector.h
new file mode 100644
index 0000000..fe49d09
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_block_collector.h
@@ -0,0 +1,122 @@
+// 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_TEST_TOOLS_HPACK_BLOCK_COLLECTOR_H_
+#define QUICHE_HTTP2_TEST_TOOLS_HPACK_BLOCK_COLLECTOR_H_
+
+// HpackBlockCollector implements HpackEntryDecoderListener in order to record
+// the calls using HpackEntryCollector instances (one per HPACK entry). This
+// supports testing of HpackBlockDecoder, which decodes entire HPACK blocks.
+//
+// In addition to implementing the callback methods, HpackBlockCollector also
+// supports comparing two HpackBlockCollector instances (i.e. an expected and
+// an actual), or a sole HPACK entry against an expected value.
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "quiche/http2/hpack/decoder/hpack_entry_decoder_listener.h"
+#include "quiche/http2/hpack/http2_hpack_constants.h"
+#include "quiche/http2/test_tools/hpack_block_builder.h"
+#include "quiche/http2/test_tools/hpack_entry_collector.h"
+#include "quiche/http2/test_tools/http2_random.h"
+#include "quiche/common/platform/api/quiche_export.h"
+
+namespace http2 {
+namespace test {
+
+class QUICHE_NO_EXPORT HpackBlockCollector : public HpackEntryDecoderListener {
+ public:
+ HpackBlockCollector();
+ HpackBlockCollector(const HpackBlockCollector& other);
+ ~HpackBlockCollector() override;
+
+ // Implementations of HpackEntryDecoderListener, forwarding to pending_entry_,
+ // an HpackEntryCollector for the "in-progress" HPACK entry. OnIndexedHeader
+ // and OnDynamicTableSizeUpdate are pending only for that one call, while
+ // OnStartLiteralHeader is followed by many calls, ending with OnValueEnd.
+ // Once all the calls for one HPACK entry have been received, PushPendingEntry
+ // is used to append the pending_entry_ entry to the collected entries_.
+ void OnIndexedHeader(size_t index) override;
+ void OnDynamicTableSizeUpdate(size_t size) override;
+ void OnStartLiteralHeader(HpackEntryType header_type,
+ size_t maybe_name_index) override;
+ void OnNameStart(bool huffman_encoded, size_t len) override;
+ void OnNameData(const char* data, size_t len) override;
+ void OnNameEnd() override;
+ void OnValueStart(bool huffman_encoded, size_t len) override;
+ void OnValueData(const char* data, size_t len) override;
+ void OnValueEnd() override;
+
+ // Methods for creating a set of expectations (i.e. HPACK entries to compare
+ // against those collected by another instance of HpackBlockCollector).
+
+ // Add an HPACK entry for an indexed header.
+ void ExpectIndexedHeader(size_t index);
+
+ // Add an HPACK entry for a dynamic table size update.
+ void ExpectDynamicTableSizeUpdate(size_t size);
+
+ // Add an HPACK entry for a header entry with an index for the name, and a
+ // literal value.
+ void ExpectNameIndexAndLiteralValue(HpackEntryType type, size_t index,
+ bool value_huffman,
+ const std::string& value);
+
+ // Add an HPACK entry for a header entry with a literal name and value.
+ void ExpectLiteralNameAndValue(HpackEntryType type, bool name_huffman,
+ const std::string& name, bool value_huffman,
+ const std::string& value);
+
+ // Shuffle the entries, in support of generating an HPACK block of entries
+ // in some random order.
+ void ShuffleEntries(Http2Random* rng);
+
+ // Serialize entries_ to the HpackBlockBuilder.
+ void AppendToHpackBlockBuilder(HpackBlockBuilder* hbb) const;
+
+ // Return AssertionSuccess if there is just one entry, and it is an
+ // Indexed Header with the specified index.
+ ::testing::AssertionResult ValidateSoleIndexedHeader(size_t ndx) const;
+
+ // Return AssertionSuccess if there is just one entry, and it is a
+ // Dynamic Table Size Update with the specified size.
+ ::testing::AssertionResult ValidateSoleDynamicTableSizeUpdate(
+ size_t size) const;
+
+ // Return AssertionSuccess if there is just one entry, and it is a Header
+ // entry with an index for the name and a literal value.
+ ::testing::AssertionResult ValidateSoleLiteralValueHeader(
+ HpackEntryType expected_type, size_t expected_index,
+ bool expected_value_huffman, absl::string_view expected_value) const;
+
+ // Return AssertionSuccess if there is just one entry, and it is a Header
+ // with a literal name and literal value.
+ ::testing::AssertionResult ValidateSoleLiteralNameValueHeader(
+ HpackEntryType expected_type, bool expected_name_huffman,
+ absl::string_view expected_name, bool expected_value_huffman,
+ absl::string_view expected_value) const;
+
+ bool IsNotPending() const { return pending_entry_.IsClear(); }
+ bool IsClear() const { return IsNotPending() && entries_.empty(); }
+ void Clear();
+
+ ::testing::AssertionResult VerifyEq(const HpackBlockCollector& that) const;
+
+ private:
+ // Push the value of pending_entry_ onto entries_, and clear pending_entry_.
+ // The pending_entry_ must be complete.
+ void PushPendingEntry();
+
+ HpackEntryCollector pending_entry_;
+ std::vector<HpackEntryCollector> entries_;
+};
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_TEST_TOOLS_HPACK_BLOCK_COLLECTOR_H_
diff --git a/quiche/http2/test_tools/hpack_entry_collector.cc b/quiche/http2/test_tools/hpack_entry_collector.cc
new file mode 100644
index 0000000..b99e35c
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_entry_collector.cc
@@ -0,0 +1,291 @@
+// 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 "quiche/http2/test_tools/hpack_entry_collector.h"
+
+#include "absl/strings/str_cat.h"
+#include "quiche/http2/hpack/http2_hpack_constants.h"
+#include "quiche/http2/test_tools/hpack_string_collector.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/platform/api/quiche_test.h"
+#include "quiche/common/platform/api/quiche_test_helpers.h"
+
+using ::testing::AssertionResult;
+
+namespace http2 {
+namespace test {
+namespace {
+
+const HpackEntryType kInvalidHeaderType = static_cast<HpackEntryType>(99);
+const size_t kInvalidIndex = 99999999;
+
+} // namespace
+
+HpackEntryCollector::HpackEntryCollector() { Clear(); }
+
+HpackEntryCollector::HpackEntryCollector(const HpackEntryCollector& other) =
+ default;
+
+HpackEntryCollector::HpackEntryCollector(HpackEntryType type,
+ size_t index_or_size)
+ : header_type_(type), index_(index_or_size), started_(true), ended_(true) {}
+HpackEntryCollector::HpackEntryCollector(HpackEntryType type, size_t index,
+ bool value_huffman,
+ const std::string& value)
+ : header_type_(type),
+ index_(index),
+ value_(value, value_huffman),
+ started_(true),
+ ended_(true) {}
+HpackEntryCollector::HpackEntryCollector(HpackEntryType type, bool name_huffman,
+ const std::string& name,
+ bool value_huffman,
+ const std::string& value)
+ : header_type_(type),
+ index_(0),
+ name_(name, name_huffman),
+ value_(value, value_huffman),
+ started_(true),
+ ended_(true) {}
+
+HpackEntryCollector::~HpackEntryCollector() = default;
+
+void HpackEntryCollector::OnIndexedHeader(size_t index) {
+ ASSERT_FALSE(started_);
+ ASSERT_TRUE(IsClear()) << ToString();
+ Init(HpackEntryType::kIndexedHeader, index);
+ ended_ = true;
+}
+void HpackEntryCollector::OnStartLiteralHeader(HpackEntryType header_type,
+ size_t maybe_name_index) {
+ ASSERT_FALSE(started_);
+ ASSERT_TRUE(IsClear()) << ToString();
+ Init(header_type, maybe_name_index);
+}
+void HpackEntryCollector::OnNameStart(bool huffman_encoded, size_t len) {
+ ASSERT_TRUE(started_);
+ ASSERT_FALSE(ended_);
+ ASSERT_FALSE(IsClear());
+ ASSERT_TRUE(LiteralNameExpected()) << ToString();
+ name_.OnStringStart(huffman_encoded, len);
+}
+void HpackEntryCollector::OnNameData(const char* data, size_t len) {
+ ASSERT_TRUE(started_);
+ ASSERT_FALSE(ended_);
+ ASSERT_TRUE(LiteralNameExpected()) << ToString();
+ ASSERT_TRUE(name_.IsInProgress());
+ name_.OnStringData(data, len);
+}
+void HpackEntryCollector::OnNameEnd() {
+ ASSERT_TRUE(started_);
+ ASSERT_FALSE(ended_);
+ ASSERT_TRUE(LiteralNameExpected()) << ToString();
+ ASSERT_TRUE(name_.IsInProgress());
+ name_.OnStringEnd();
+}
+void HpackEntryCollector::OnValueStart(bool huffman_encoded, size_t len) {
+ ASSERT_TRUE(started_);
+ ASSERT_FALSE(ended_);
+ if (LiteralNameExpected()) {
+ ASSERT_TRUE(name_.HasEnded());
+ }
+ ASSERT_TRUE(LiteralValueExpected()) << ToString();
+ ASSERT_TRUE(value_.IsClear()) << value_.ToString();
+ value_.OnStringStart(huffman_encoded, len);
+}
+void HpackEntryCollector::OnValueData(const char* data, size_t len) {
+ ASSERT_TRUE(started_);
+ ASSERT_FALSE(ended_);
+ ASSERT_TRUE(LiteralValueExpected()) << ToString();
+ ASSERT_TRUE(value_.IsInProgress());
+ value_.OnStringData(data, len);
+}
+void HpackEntryCollector::OnValueEnd() {
+ ASSERT_TRUE(started_);
+ ASSERT_FALSE(ended_);
+ ASSERT_TRUE(LiteralValueExpected()) << ToString();
+ ASSERT_TRUE(value_.IsInProgress());
+ value_.OnStringEnd();
+ ended_ = true;
+}
+void HpackEntryCollector::OnDynamicTableSizeUpdate(size_t size) {
+ ASSERT_FALSE(started_);
+ ASSERT_TRUE(IsClear()) << ToString();
+ Init(HpackEntryType::kDynamicTableSizeUpdate, size);
+ ended_ = true;
+}
+
+void HpackEntryCollector::Clear() {
+ header_type_ = kInvalidHeaderType;
+ index_ = kInvalidIndex;
+ name_.Clear();
+ value_.Clear();
+ started_ = ended_ = false;
+}
+bool HpackEntryCollector::IsClear() const {
+ return header_type_ == kInvalidHeaderType && index_ == kInvalidIndex &&
+ name_.IsClear() && value_.IsClear() && !started_ && !ended_;
+}
+bool HpackEntryCollector::IsComplete() const { return started_ && ended_; }
+bool HpackEntryCollector::LiteralNameExpected() const {
+ switch (header_type_) {
+ case HpackEntryType::kIndexedLiteralHeader:
+ case HpackEntryType::kUnindexedLiteralHeader:
+ case HpackEntryType::kNeverIndexedLiteralHeader:
+ return index_ == 0;
+ default:
+ return false;
+ }
+}
+bool HpackEntryCollector::LiteralValueExpected() const {
+ switch (header_type_) {
+ case HpackEntryType::kIndexedLiteralHeader:
+ case HpackEntryType::kUnindexedLiteralHeader:
+ case HpackEntryType::kNeverIndexedLiteralHeader:
+ return true;
+ default:
+ return false;
+ }
+}
+AssertionResult HpackEntryCollector::ValidateIndexedHeader(
+ size_t expected_index) const {
+ VERIFY_TRUE(started_);
+ VERIFY_TRUE(ended_);
+ VERIFY_EQ(HpackEntryType::kIndexedHeader, header_type_);
+ VERIFY_EQ(expected_index, index_);
+ return ::testing::AssertionSuccess();
+}
+AssertionResult HpackEntryCollector::ValidateLiteralValueHeader(
+ HpackEntryType expected_type, size_t expected_index,
+ bool expected_value_huffman, absl::string_view expected_value) const {
+ VERIFY_TRUE(started_);
+ VERIFY_TRUE(ended_);
+ VERIFY_EQ(expected_type, header_type_);
+ VERIFY_NE(0u, expected_index);
+ VERIFY_EQ(expected_index, index_);
+ VERIFY_TRUE(name_.IsClear());
+ VERIFY_SUCCESS(value_.Collected(expected_value, expected_value_huffman));
+ return ::testing::AssertionSuccess();
+}
+AssertionResult HpackEntryCollector::ValidateLiteralNameValueHeader(
+ HpackEntryType expected_type, bool expected_name_huffman,
+ absl::string_view expected_name, bool expected_value_huffman,
+ absl::string_view expected_value) const {
+ VERIFY_TRUE(started_);
+ VERIFY_TRUE(ended_);
+ VERIFY_EQ(expected_type, header_type_);
+ VERIFY_EQ(0u, index_);
+ VERIFY_SUCCESS(name_.Collected(expected_name, expected_name_huffman));
+ VERIFY_SUCCESS(value_.Collected(expected_value, expected_value_huffman));
+ return ::testing::AssertionSuccess();
+}
+AssertionResult HpackEntryCollector::ValidateDynamicTableSizeUpdate(
+ size_t size) const {
+ VERIFY_TRUE(started_);
+ VERIFY_TRUE(ended_);
+ VERIFY_EQ(HpackEntryType::kDynamicTableSizeUpdate, header_type_);
+ VERIFY_EQ(index_, size);
+ return ::testing::AssertionSuccess();
+}
+
+void HpackEntryCollector::AppendToHpackBlockBuilder(
+ HpackBlockBuilder* hbb) const {
+ ASSERT_TRUE(started_ && ended_) << *this;
+ switch (header_type_) {
+ case HpackEntryType::kIndexedHeader:
+ hbb->AppendIndexedHeader(index_);
+ return;
+
+ case HpackEntryType::kDynamicTableSizeUpdate:
+ hbb->AppendDynamicTableSizeUpdate(index_);
+ return;
+
+ case HpackEntryType::kIndexedLiteralHeader:
+ case HpackEntryType::kUnindexedLiteralHeader:
+ case HpackEntryType::kNeverIndexedLiteralHeader:
+ ASSERT_TRUE(value_.HasEnded()) << *this;
+ if (index_ != 0) {
+ QUICHE_CHECK(name_.IsClear());
+ hbb->AppendNameIndexAndLiteralValue(header_type_, index_,
+ value_.huffman_encoded, value_.s);
+ } else {
+ QUICHE_CHECK(name_.HasEnded()) << *this;
+ hbb->AppendLiteralNameAndValue(header_type_, name_.huffman_encoded,
+ name_.s, value_.huffman_encoded,
+ value_.s);
+ }
+ return;
+
+ default:
+ ADD_FAILURE() << *this;
+ }
+}
+
+std::string HpackEntryCollector::ToString() const {
+ std::string result("Type=");
+ switch (header_type_) {
+ case HpackEntryType::kIndexedHeader:
+ result += "IndexedHeader";
+ break;
+ case HpackEntryType::kDynamicTableSizeUpdate:
+ result += "DynamicTableSizeUpdate";
+ break;
+ case HpackEntryType::kIndexedLiteralHeader:
+ result += "IndexedLiteralHeader";
+ break;
+ case HpackEntryType::kUnindexedLiteralHeader:
+ result += "UnindexedLiteralHeader";
+ break;
+ case HpackEntryType::kNeverIndexedLiteralHeader:
+ result += "NeverIndexedLiteralHeader";
+ break;
+ default:
+ if (header_type_ == kInvalidHeaderType) {
+ result += "<unset>";
+ } else {
+ absl::StrAppend(&result, header_type_);
+ }
+ }
+ if (index_ != 0) {
+ absl::StrAppend(&result, " Index=", index_);
+ }
+ if (!name_.IsClear()) {
+ absl::StrAppend(&result, " Name", name_.ToString());
+ }
+ if (!value_.IsClear()) {
+ absl::StrAppend(&result, " Value", value_.ToString());
+ }
+ if (!started_) {
+ EXPECT_FALSE(ended_);
+ absl::StrAppend(&result, " !started");
+ } else if (!ended_) {
+ absl::StrAppend(&result, " !ended");
+ } else {
+ absl::StrAppend(&result, " Complete");
+ }
+ return result;
+}
+
+void HpackEntryCollector::Init(HpackEntryType type, size_t maybe_index) {
+ ASSERT_TRUE(IsClear()) << ToString();
+ header_type_ = type;
+ index_ = maybe_index;
+ started_ = true;
+}
+
+bool operator==(const HpackEntryCollector& a, const HpackEntryCollector& b) {
+ return a.name() == b.name() && a.value() == b.value() &&
+ a.index() == b.index() && a.header_type() == b.header_type() &&
+ a.started() == b.started() && a.ended() == b.ended();
+}
+bool operator!=(const HpackEntryCollector& a, const HpackEntryCollector& b) {
+ return !(a == b);
+}
+
+std::ostream& operator<<(std::ostream& out, const HpackEntryCollector& v) {
+ return out << v.ToString();
+}
+
+} // namespace test
+} // namespace http2
diff --git a/quiche/http2/test_tools/hpack_entry_collector.h b/quiche/http2/test_tools/hpack_entry_collector.h
new file mode 100644
index 0000000..93ad860
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_entry_collector.h
@@ -0,0 +1,151 @@
+// 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_TEST_TOOLS_HPACK_ENTRY_COLLECTOR_H_
+#define QUICHE_HTTP2_TEST_TOOLS_HPACK_ENTRY_COLLECTOR_H_
+
+// HpackEntryCollector records calls to HpackEntryDecoderListener in support
+// of tests of HpackEntryDecoder, or which use it. Can only record the callbacks
+// for the decoding of a single entry; call Clear() between decoding successive
+// entries or use a distinct HpackEntryCollector for each entry.
+
+#include <stddef.h>
+
+#include <iosfwd>
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "quiche/http2/hpack/decoder/hpack_entry_decoder_listener.h"
+#include "quiche/http2/hpack/http2_hpack_constants.h"
+#include "quiche/http2/test_tools/hpack_block_builder.h"
+#include "quiche/http2/test_tools/hpack_string_collector.h"
+#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace test {
+
+class QUICHE_NO_EXPORT HpackEntryCollector : public HpackEntryDecoderListener {
+ public:
+ HpackEntryCollector();
+ HpackEntryCollector(const HpackEntryCollector& other);
+
+ // These next three constructors are intended for use in tests that create
+ // an HpackEntryCollector "manually", and then compare it against another
+ // that is populated via calls to the HpackEntryDecoderListener methods.
+ HpackEntryCollector(HpackEntryType type, size_t index_or_size);
+ HpackEntryCollector(HpackEntryType type, size_t index, bool value_huffman,
+ const std::string& value);
+ HpackEntryCollector(HpackEntryType type, bool name_huffman,
+ const std::string& name, bool value_huffman,
+ const std::string& value);
+
+ ~HpackEntryCollector() override;
+
+ // Methods defined by HpackEntryDecoderListener.
+ void OnIndexedHeader(size_t index) override;
+ void OnStartLiteralHeader(HpackEntryType header_type,
+ size_t maybe_name_index) override;
+ void OnNameStart(bool huffman_encoded, size_t len) override;
+ void OnNameData(const char* data, size_t len) override;
+ void OnNameEnd() override;
+ void OnValueStart(bool huffman_encoded, size_t len) override;
+ void OnValueData(const char* data, size_t len) override;
+ void OnValueEnd() override;
+ void OnDynamicTableSizeUpdate(size_t size) override;
+
+ // Clears the fields of the collector so that it is ready to start collecting
+ // another HPACK block entry.
+ void Clear();
+
+ // Is the collector ready to start collecting another HPACK block entry.
+ bool IsClear() const;
+
+ // Has a complete entry been collected?
+ bool IsComplete() const;
+
+ // Based on the HpackEntryType, is a literal name expected?
+ bool LiteralNameExpected() const;
+
+ // Based on the HpackEntryType, is a literal value expected?
+ bool LiteralValueExpected() const;
+
+ // Returns success if collected an Indexed Header (i.e. OnIndexedHeader was
+ // called).
+ ::testing::AssertionResult ValidateIndexedHeader(size_t expected_index) const;
+
+ // Returns success if collected a Header with an indexed name and literal
+ // value (i.e. OnStartLiteralHeader was called with a non-zero index for
+ // the name, which must match expected_index).
+ ::testing::AssertionResult ValidateLiteralValueHeader(
+ HpackEntryType expected_type, size_t expected_index,
+ bool expected_value_huffman, absl::string_view expected_value) const;
+
+ // Returns success if collected a Header with an literal name and literal
+ // value.
+ ::testing::AssertionResult ValidateLiteralNameValueHeader(
+ HpackEntryType expected_type, bool expected_name_huffman,
+ absl::string_view expected_name, bool expected_value_huffman,
+ absl::string_view expected_value) const;
+
+ // Returns success if collected a Dynamic Table Size Update,
+ // with the specified size.
+ ::testing::AssertionResult ValidateDynamicTableSizeUpdate(
+ size_t expected_size) const;
+
+ void set_header_type(HpackEntryType v) { header_type_ = v; }
+ HpackEntryType header_type() const { return header_type_; }
+
+ void set_index(size_t v) { index_ = v; }
+ size_t index() const { return index_; }
+
+ void set_name(const HpackStringCollector& v) { name_ = v; }
+ const HpackStringCollector& name() const { return name_; }
+
+ void set_value(const HpackStringCollector& v) { value_ = v; }
+ const HpackStringCollector& value() const { return value_; }
+
+ void set_started(bool v) { started_ = v; }
+ bool started() const { return started_; }
+
+ void set_ended(bool v) { ended_ = v; }
+ bool ended() const { return ended_; }
+
+ void AppendToHpackBlockBuilder(HpackBlockBuilder* hbb) const;
+
+ // Returns a debug string.
+ std::string ToString() const;
+
+ private:
+ void Init(HpackEntryType type, size_t maybe_index);
+
+ HpackEntryType header_type_;
+ size_t index_;
+
+ HpackStringCollector name_;
+ HpackStringCollector value_;
+
+ // True if has received a call to an HpackEntryDecoderListener method
+ // indicating the start of decoding an HPACK entry; for example,
+ // OnIndexedHeader set it true, but OnNameStart does not change it.
+ bool started_ = false;
+
+ // True if has received a call to an HpackEntryDecoderListener method
+ // indicating the end of decoding an HPACK entry; for example,
+ // OnIndexedHeader and OnValueEnd both set it true, but OnNameEnd does
+ // not change it.
+ bool ended_ = false;
+};
+
+QUICHE_NO_EXPORT bool operator==(const HpackEntryCollector& a,
+ const HpackEntryCollector& b);
+QUICHE_NO_EXPORT bool operator!=(const HpackEntryCollector& a,
+ const HpackEntryCollector& b);
+QUICHE_NO_EXPORT std::ostream& operator<<(std::ostream& out,
+ const HpackEntryCollector& v);
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_TEST_TOOLS_HPACK_ENTRY_COLLECTOR_H_
diff --git a/quiche/http2/test_tools/hpack_example.cc b/quiche/http2/test_tools/hpack_example.cc
new file mode 100644
index 0000000..42d9261
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_example.cc
@@ -0,0 +1,59 @@
+// 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 "quiche/http2/test_tools/hpack_example.h"
+
+#include <ctype.h>
+
+#include "absl/strings/escaping.h"
+#include "absl/strings/str_cat.h"
+#include "quiche/common/platform/api/quiche_bug_tracker.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+
+namespace http2 {
+namespace test {
+namespace {
+
+void HpackExampleToStringOrDie(absl::string_view example, std::string* output) {
+ while (!example.empty()) {
+ const char c0 = example[0];
+ if (isxdigit(c0)) {
+ QUICHE_CHECK_GT(example.size(), 1u) << "Truncated hex byte?";
+ const char c1 = example[1];
+ QUICHE_CHECK(isxdigit(c1)) << "Found half a byte?";
+ *output += absl::HexStringToBytes(example.substr(0, 2));
+ example.remove_prefix(2);
+ continue;
+ }
+ if (isspace(c0)) {
+ example.remove_prefix(1);
+ continue;
+ }
+ if (!example.empty() && example[0] == '|') {
+ // Start of a comment. Skip to end of line or of input.
+ auto pos = example.find('\n');
+ if (pos == absl::string_view::npos) {
+ // End of input.
+ break;
+ }
+ example.remove_prefix(pos + 1);
+ continue;
+ }
+ QUICHE_BUG(http2_bug_107_1)
+ << "Can't parse byte " << static_cast<int>(c0)
+ << absl::StrCat(" (0x", absl::Hex(c0), ")") << "\nExample: " << example;
+ }
+ QUICHE_CHECK_LT(0u, output->size()) << "Example is empty.";
+}
+
+} // namespace
+
+std::string HpackExampleToStringOrDie(absl::string_view example) {
+ std::string output;
+ HpackExampleToStringOrDie(example, &output);
+ return output;
+}
+
+} // namespace test
+} // namespace http2
diff --git a/quiche/http2/test_tools/hpack_example.h b/quiche/http2/test_tools/hpack_example.h
new file mode 100644
index 0000000..007b969
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_example.h
@@ -0,0 +1,32 @@
+// 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_TEST_TOOLS_HPACK_EXAMPLE_H_
+#define QUICHE_HTTP2_TEST_TOOLS_HPACK_EXAMPLE_H_
+
+#include <string>
+
+#include "absl/strings/string_view.h"
+
+// Parses HPACK examples in the format seen in the HPACK specification,
+// RFC 7541. For example:
+//
+// 10 | == Literal never indexed ==
+// 08 | Literal name (len = 8)
+// 7061 7373 776f 7264 | password
+// 06 | Literal value (len = 6)
+// 7365 6372 6574 | secret
+// | -> password: secret
+//
+// (excluding the leading "//").
+
+namespace http2 {
+namespace test {
+
+std::string HpackExampleToStringOrDie(absl::string_view example);
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_TEST_TOOLS_HPACK_EXAMPLE_H_
diff --git a/quiche/http2/test_tools/hpack_example_test.cc b/quiche/http2/test_tools/hpack_example_test.cc
new file mode 100644
index 0000000..4dd24bd
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_example_test.cc
@@ -0,0 +1,45 @@
+#include "quiche/http2/test_tools/hpack_example.h"
+
+// Tests of HpackExampleToStringOrDie.
+
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace test {
+namespace {
+
+TEST(HpackExampleToStringOrDie, GoodInput) {
+ std::string bytes = HpackExampleToStringOrDie(R"(
+ 40 | == Literal never indexed ==
+ | Blank lines are OK in example:
+
+ 08 | Literal name (len = 8)
+ 7061 7373 776f 7264 | password
+ 06 | Literal value (len = 6)
+ 7365 6372 6574 | secret
+ | -> password: secret
+ )");
+
+ // clang-format off
+ const char kExpected[] = {
+ 0x40, // Never Indexed, Literal Name and Value
+ 0x08, // Name Len: 8
+ 0x70, 0x61, 0x73, 0x73, // Name: password
+ 0x77, 0x6f, 0x72, 0x64, //
+ 0x06, // Value Len: 6
+ 0x73, 0x65, 0x63, 0x72, // Value: secret
+ 0x65, 0x74, //
+ };
+ // clang-format on
+ EXPECT_EQ(absl::string_view(kExpected, sizeof kExpected), bytes);
+}
+
+TEST(HpackExampleToStringOrDie, InvalidInput) {
+ EXPECT_DEATH(HpackExampleToStringOrDie("4"), "Truncated");
+ EXPECT_DEATH(HpackExampleToStringOrDie("4x"), "half");
+ EXPECT_DEATH(HpackExampleToStringOrDie(""), "empty");
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/quiche/http2/test_tools/hpack_string_collector.cc b/quiche/http2/test_tools/hpack_string_collector.cc
new file mode 100644
index 0000000..63a78b2
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_string_collector.cc
@@ -0,0 +1,117 @@
+// 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 "quiche/http2/test_tools/hpack_string_collector.h"
+
+#include <stddef.h>
+
+#include <iosfwd>
+#include <ostream>
+
+#include "absl/strings/escaping.h"
+#include "absl/strings/str_cat.h"
+#include "quiche/common/platform/api/quiche_test.h"
+#include "quiche/common/platform/api/quiche_test_helpers.h"
+
+namespace http2 {
+namespace test {
+namespace {
+
+std::ostream& operator<<(std::ostream& out,
+ HpackStringCollector::CollectorState v) {
+ switch (v) {
+ case HpackStringCollector::CollectorState::kGenesis:
+ return out << "kGenesis";
+ case HpackStringCollector::CollectorState::kStarted:
+ return out << "kStarted";
+ case HpackStringCollector::CollectorState::kEnded:
+ return out << "kEnded";
+ }
+ return out << "UnknownCollectorState";
+}
+
+} // namespace
+
+HpackStringCollector::HpackStringCollector() { Clear(); }
+
+HpackStringCollector::HpackStringCollector(const std::string& str, bool huffman)
+ : s(str), len(str.size()), huffman_encoded(huffman), state(kEnded) {}
+
+void HpackStringCollector::Clear() {
+ s = "";
+ len = 0;
+ huffman_encoded = false;
+ state = kGenesis;
+}
+
+bool HpackStringCollector::IsClear() const {
+ return s.empty() && len == 0 && huffman_encoded == false && state == kGenesis;
+}
+
+bool HpackStringCollector::IsInProgress() const { return state == kStarted; }
+
+bool HpackStringCollector::HasEnded() const { return state == kEnded; }
+
+void HpackStringCollector::OnStringStart(bool huffman, size_t length) {
+ EXPECT_TRUE(IsClear()) << ToString();
+ state = kStarted;
+ huffman_encoded = huffman;
+ len = length;
+}
+
+void HpackStringCollector::OnStringData(const char* data, size_t length) {
+ absl::string_view sp(data, length);
+ EXPECT_TRUE(IsInProgress()) << ToString();
+ EXPECT_LE(sp.size(), len) << ToString();
+ absl::StrAppend(&s, sp);
+ EXPECT_LE(s.size(), len) << ToString();
+}
+
+void HpackStringCollector::OnStringEnd() {
+ EXPECT_TRUE(IsInProgress()) << ToString();
+ EXPECT_EQ(s.size(), len) << ToString();
+ state = kEnded;
+}
+
+::testing::AssertionResult HpackStringCollector::Collected(
+ absl::string_view str, bool is_huffman_encoded) const {
+ VERIFY_TRUE(HasEnded());
+ VERIFY_EQ(str.size(), len);
+ VERIFY_EQ(is_huffman_encoded, huffman_encoded);
+ VERIFY_EQ(str, s);
+ return ::testing::AssertionSuccess();
+}
+
+std::string HpackStringCollector::ToString() const {
+ std::stringstream ss;
+ ss << *this;
+ return ss.str();
+}
+
+bool operator==(const HpackStringCollector& a, const HpackStringCollector& b) {
+ return a.s == b.s && a.len == b.len &&
+ a.huffman_encoded == b.huffman_encoded && a.state == b.state;
+}
+
+bool operator!=(const HpackStringCollector& a, const HpackStringCollector& b) {
+ return !(a == b);
+}
+
+std::ostream& operator<<(std::ostream& out, const HpackStringCollector& v) {
+ out << "HpackStringCollector(state=" << v.state;
+ if (v.state == HpackStringCollector::kGenesis) {
+ return out << ")";
+ }
+ if (v.huffman_encoded) {
+ out << ", Huffman Encoded";
+ }
+ out << ", Length=" << v.len;
+ if (!v.s.empty() && v.len != v.s.size()) {
+ out << " (" << v.s.size() << ")";
+ }
+ return out << ", String=\"" << absl::CHexEscape(v.s) << "\")";
+}
+
+} // namespace test
+} // namespace http2
diff --git a/quiche/http2/test_tools/hpack_string_collector.h b/quiche/http2/test_tools/hpack_string_collector.h
new file mode 100644
index 0000000..15dab67
--- /dev/null
+++ b/quiche/http2/test_tools/hpack_string_collector.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_TEST_TOOLS_HPACK_STRING_COLLECTOR_H_
+#define QUICHE_HTTP2_TEST_TOOLS_HPACK_STRING_COLLECTOR_H_
+
+// Supports tests of decoding HPACK strings.
+
+#include <stddef.h>
+
+#include <iosfwd>
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "quiche/http2/hpack/decoder/hpack_string_decoder_listener.h"
+#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace test {
+
+// Records the callbacks associated with a decoding a string; must
+// call Clear() between decoding successive strings.
+struct QUICHE_NO_EXPORT HpackStringCollector
+ : public HpackStringDecoderListener {
+ enum CollectorState {
+ kGenesis,
+ kStarted,
+ kEnded,
+ };
+
+ HpackStringCollector();
+ HpackStringCollector(const std::string& str, bool huffman);
+
+ void Clear();
+ bool IsClear() const;
+ bool IsInProgress() const;
+ bool HasEnded() const;
+
+ void OnStringStart(bool huffman, size_t length) override;
+ void OnStringData(const char* data, size_t length) override;
+ void OnStringEnd() override;
+
+ ::testing::AssertionResult Collected(absl::string_view str,
+ bool is_huffman_encoded) const;
+
+ std::string ToString() const;
+
+ std::string s;
+ size_t len;
+ bool huffman_encoded;
+ CollectorState state;
+};
+
+bool operator==(const HpackStringCollector& a, const HpackStringCollector& b);
+
+bool operator!=(const HpackStringCollector& a, const HpackStringCollector& b);
+
+QUICHE_NO_EXPORT std::ostream& operator<<(std::ostream& out,
+ const HpackStringCollector& v);
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_TEST_TOOLS_HPACK_STRING_COLLECTOR_H_
diff --git a/quiche/http2/test_tools/http2_constants_test_util.cc b/quiche/http2/test_tools/http2_constants_test_util.cc
new file mode 100644
index 0000000..ddb5cbd
--- /dev/null
+++ b/quiche/http2/test_tools/http2_constants_test_util.cc
@@ -0,0 +1,84 @@
+// 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 "quiche/http2/test_tools/http2_constants_test_util.h"
+
+namespace http2 {
+namespace test {
+
+std::vector<Http2ErrorCode> AllHttp2ErrorCodes() {
+ // clang-format off
+ return {
+ Http2ErrorCode::HTTP2_NO_ERROR,
+ Http2ErrorCode::PROTOCOL_ERROR,
+ Http2ErrorCode::INTERNAL_ERROR,
+ Http2ErrorCode::FLOW_CONTROL_ERROR,
+ Http2ErrorCode::SETTINGS_TIMEOUT,
+ Http2ErrorCode::STREAM_CLOSED,
+ Http2ErrorCode::FRAME_SIZE_ERROR,
+ Http2ErrorCode::REFUSED_STREAM,
+ Http2ErrorCode::CANCEL,
+ Http2ErrorCode::COMPRESSION_ERROR,
+ Http2ErrorCode::CONNECT_ERROR,
+ Http2ErrorCode::ENHANCE_YOUR_CALM,
+ Http2ErrorCode::INADEQUATE_SECURITY,
+ Http2ErrorCode::HTTP_1_1_REQUIRED,
+ };
+ // clang-format on
+}
+
+std::vector<Http2SettingsParameter> AllHttp2SettingsParameters() {
+ // clang-format off
+ return {
+ Http2SettingsParameter::HEADER_TABLE_SIZE,
+ Http2SettingsParameter::ENABLE_PUSH,
+ Http2SettingsParameter::MAX_CONCURRENT_STREAMS,
+ Http2SettingsParameter::INITIAL_WINDOW_SIZE,
+ Http2SettingsParameter::MAX_FRAME_SIZE,
+ Http2SettingsParameter::MAX_HEADER_LIST_SIZE,
+ };
+ // clang-format on
+}
+
+// Returns a mask of flags supported for the specified frame type. Returns
+// zero for unknown frame types.
+uint8_t KnownFlagsMaskForFrameType(Http2FrameType type) {
+ switch (type) {
+ case Http2FrameType::DATA:
+ return Http2FrameFlag::END_STREAM | Http2FrameFlag::PADDED;
+ case Http2FrameType::HEADERS:
+ return Http2FrameFlag::END_STREAM | Http2FrameFlag::END_HEADERS |
+ Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY;
+ case Http2FrameType::PRIORITY:
+ return 0x00;
+ case Http2FrameType::RST_STREAM:
+ return 0x00;
+ case Http2FrameType::SETTINGS:
+ return Http2FrameFlag::ACK;
+ case Http2FrameType::PUSH_PROMISE:
+ return Http2FrameFlag::END_HEADERS | Http2FrameFlag::PADDED;
+ case Http2FrameType::PING:
+ return Http2FrameFlag::ACK;
+ case Http2FrameType::GOAWAY:
+ return 0x00;
+ case Http2FrameType::WINDOW_UPDATE:
+ return 0x00;
+ case Http2FrameType::CONTINUATION:
+ return Http2FrameFlag::END_HEADERS;
+ case Http2FrameType::ALTSVC:
+ return 0x00;
+ default:
+ return 0x00;
+ }
+}
+
+uint8_t InvalidFlagMaskForFrameType(Http2FrameType type) {
+ if (IsSupportedHttp2FrameType(type)) {
+ return ~KnownFlagsMaskForFrameType(type);
+ }
+ return 0x00;
+}
+
+} // namespace test
+} // namespace http2
diff --git a/quiche/http2/test_tools/http2_constants_test_util.h b/quiche/http2/test_tools/http2_constants_test_util.h
new file mode 100644
index 0000000..20edc8d
--- /dev/null
+++ b/quiche/http2/test_tools/http2_constants_test_util.h
@@ -0,0 +1,34 @@
+// 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_TEST_TOOLS_HTTP2_CONSTANTS_TEST_UTIL_H_
+#define QUICHE_HTTP2_TEST_TOOLS_HTTP2_CONSTANTS_TEST_UTIL_H_
+
+#include <cstdint>
+#include <vector>
+
+#include "quiche/http2/http2_constants.h"
+
+namespace http2 {
+namespace test {
+
+// Returns a vector of all supported RST_STREAM and GOAWAY error codes.
+std::vector<Http2ErrorCode> AllHttp2ErrorCodes();
+
+// Returns a vector of all supported parameters in SETTINGS frames.
+std::vector<Http2SettingsParameter> AllHttp2SettingsParameters();
+
+// Returns a mask of flags supported for the specified frame type. Returns
+// zero for unknown frame types.
+uint8_t KnownFlagsMaskForFrameType(Http2FrameType type);
+
+// Returns a mask of flag bits known to be invalid for the frame type.
+// For unknown frame types, the mask is zero; i.e., we don't know that any
+// are invalid.
+uint8_t InvalidFlagMaskForFrameType(Http2FrameType type);
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_TEST_TOOLS_HTTP2_CONSTANTS_TEST_UTIL_H_
diff --git a/quiche/http2/test_tools/http2_frame_decoder_listener_test_util.cc b/quiche/http2/test_tools/http2_frame_decoder_listener_test_util.cc
new file mode 100644
index 0000000..fea56f2
--- /dev/null
+++ b/quiche/http2/test_tools/http2_frame_decoder_listener_test_util.cc
@@ -0,0 +1,511 @@
+// 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 "quiche/http2/test_tools/http2_frame_decoder_listener_test_util.h"
+
+#include "quiche/http2/decoder/http2_frame_decoder_listener.h"
+#include "quiche/http2/http2_constants.h"
+#include "quiche/http2/http2_structures.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace http2 {
+
+FailingHttp2FrameDecoderListener::FailingHttp2FrameDecoderListener() = default;
+FailingHttp2FrameDecoderListener::~FailingHttp2FrameDecoderListener() = default;
+
+bool FailingHttp2FrameDecoderListener::OnFrameHeader(
+ const Http2FrameHeader& header) {
+ ADD_FAILURE() << "OnFrameHeader: " << header;
+ return false;
+}
+
+void FailingHttp2FrameDecoderListener::OnDataStart(
+ const Http2FrameHeader& header) {
+ FAIL() << "OnDataStart: " << header;
+}
+
+void FailingHttp2FrameDecoderListener::OnDataPayload(const char* /*data*/,
+ size_t len) {
+ FAIL() << "OnDataPayload: len=" << len;
+}
+
+void FailingHttp2FrameDecoderListener::OnDataEnd() { FAIL() << "OnDataEnd"; }
+
+void FailingHttp2FrameDecoderListener::OnHeadersStart(
+ const Http2FrameHeader& header) {
+ FAIL() << "OnHeadersStart: " << header;
+}
+
+void FailingHttp2FrameDecoderListener::OnHeadersPriority(
+ const Http2PriorityFields& priority) {
+ FAIL() << "OnHeadersPriority: " << priority;
+}
+
+void FailingHttp2FrameDecoderListener::OnHpackFragment(const char* /*data*/,
+ size_t len) {
+ FAIL() << "OnHpackFragment: len=" << len;
+}
+
+void FailingHttp2FrameDecoderListener::OnHeadersEnd() {
+ FAIL() << "OnHeadersEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnPriorityFrame(
+ const Http2FrameHeader& header, const Http2PriorityFields& priority) {
+ FAIL() << "OnPriorityFrame: " << header << "; priority: " << priority;
+}
+
+void FailingHttp2FrameDecoderListener::OnContinuationStart(
+ const Http2FrameHeader& header) {
+ FAIL() << "OnContinuationStart: " << header;
+}
+
+void FailingHttp2FrameDecoderListener::OnContinuationEnd() {
+ FAIL() << "OnContinuationEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnPadLength(size_t trailing_length) {
+ FAIL() << "OnPadLength: trailing_length=" << trailing_length;
+}
+
+void FailingHttp2FrameDecoderListener::OnPadding(const char* /*padding*/,
+ size_t skipped_length) {
+ FAIL() << "OnPadding: skipped_length=" << skipped_length;
+}
+
+void FailingHttp2FrameDecoderListener::OnRstStream(
+ const Http2FrameHeader& header, Http2ErrorCode error_code) {
+ FAIL() << "OnRstStream: " << header << "; code=" << error_code;
+}
+
+void FailingHttp2FrameDecoderListener::OnSettingsStart(
+ const Http2FrameHeader& header) {
+ FAIL() << "OnSettingsStart: " << header;
+}
+
+void FailingHttp2FrameDecoderListener::OnSetting(
+ const Http2SettingFields& setting_fields) {
+ FAIL() << "OnSetting: " << setting_fields;
+}
+
+void FailingHttp2FrameDecoderListener::OnSettingsEnd() {
+ FAIL() << "OnSettingsEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnSettingsAck(
+ const Http2FrameHeader& header) {
+ FAIL() << "OnSettingsAck: " << header;
+}
+
+void FailingHttp2FrameDecoderListener::OnPushPromiseStart(
+ const Http2FrameHeader& header, const Http2PushPromiseFields& promise,
+ size_t total_padding_length) {
+ FAIL() << "OnPushPromiseStart: " << header << "; promise: " << promise
+ << "; total_padding_length: " << total_padding_length;
+}
+
+void FailingHttp2FrameDecoderListener::OnPushPromiseEnd() {
+ FAIL() << "OnPushPromiseEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnPing(const Http2FrameHeader& header,
+ const Http2PingFields& ping) {
+ FAIL() << "OnPing: " << header << "; ping: " << ping;
+}
+
+void FailingHttp2FrameDecoderListener::OnPingAck(const Http2FrameHeader& header,
+ const Http2PingFields& ping) {
+ FAIL() << "OnPingAck: " << header << "; ping: " << ping;
+}
+
+void FailingHttp2FrameDecoderListener::OnGoAwayStart(
+ const Http2FrameHeader& header, const Http2GoAwayFields& goaway) {
+ FAIL() << "OnGoAwayStart: " << header << "; goaway: " << goaway;
+}
+
+void FailingHttp2FrameDecoderListener::OnGoAwayOpaqueData(const char* /*data*/,
+ size_t len) {
+ FAIL() << "OnGoAwayOpaqueData: len=" << len;
+}
+
+void FailingHttp2FrameDecoderListener::OnGoAwayEnd() {
+ FAIL() << "OnGoAwayEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnWindowUpdate(
+ const Http2FrameHeader& header, uint32_t increment) {
+ FAIL() << "OnWindowUpdate: " << header << "; increment=" << increment;
+}
+
+void FailingHttp2FrameDecoderListener::OnAltSvcStart(
+ const Http2FrameHeader& header, size_t origin_length, size_t value_length) {
+ FAIL() << "OnAltSvcStart: " << header << "; origin_length: " << origin_length
+ << "; value_length: " << value_length;
+}
+
+void FailingHttp2FrameDecoderListener::OnAltSvcOriginData(const char* /*data*/,
+ size_t len) {
+ FAIL() << "OnAltSvcOriginData: len=" << len;
+}
+
+void FailingHttp2FrameDecoderListener::OnAltSvcValueData(const char* /*data*/,
+ size_t len) {
+ FAIL() << "OnAltSvcValueData: len=" << len;
+}
+
+void FailingHttp2FrameDecoderListener::OnAltSvcEnd() {
+ FAIL() << "OnAltSvcEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnPriorityUpdateStart(
+ const Http2FrameHeader& header,
+ const Http2PriorityUpdateFields& priority_update) {
+ FAIL() << "OnPriorityUpdateStart: " << header << "; prioritized_stream_id: "
+ << priority_update.prioritized_stream_id;
+}
+
+void FailingHttp2FrameDecoderListener::OnPriorityUpdatePayload(
+ const char* /*data*/, size_t len) {
+ FAIL() << "OnPriorityUpdatePayload: len=" << len;
+}
+
+void FailingHttp2FrameDecoderListener::OnPriorityUpdateEnd() {
+ FAIL() << "OnPriorityUpdateEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnUnknownStart(
+ const Http2FrameHeader& header) {
+ FAIL() << "OnUnknownStart: " << header;
+}
+
+void FailingHttp2FrameDecoderListener::OnUnknownPayload(const char* /*data*/,
+ size_t len) {
+ FAIL() << "OnUnknownPayload: len=" << len;
+}
+
+void FailingHttp2FrameDecoderListener::OnUnknownEnd() {
+ FAIL() << "OnUnknownEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnPaddingTooLong(
+ const Http2FrameHeader& header, size_t missing_length) {
+ FAIL() << "OnPaddingTooLong: " << header
+ << "; missing_length: " << missing_length;
+}
+
+void FailingHttp2FrameDecoderListener::OnFrameSizeError(
+ const Http2FrameHeader& header) {
+ FAIL() << "OnFrameSizeError: " << header;
+}
+
+LoggingHttp2FrameDecoderListener::LoggingHttp2FrameDecoderListener()
+ : wrapped_(nullptr) {}
+LoggingHttp2FrameDecoderListener::LoggingHttp2FrameDecoderListener(
+ Http2FrameDecoderListener* wrapped)
+ : wrapped_(wrapped) {}
+LoggingHttp2FrameDecoderListener::~LoggingHttp2FrameDecoderListener() = default;
+
+bool LoggingHttp2FrameDecoderListener::OnFrameHeader(
+ const Http2FrameHeader& header) {
+ QUICHE_VLOG(1) << "OnFrameHeader: " << header;
+ if (wrapped_ != nullptr) {
+ return wrapped_->OnFrameHeader(header);
+ }
+ return true;
+}
+
+void LoggingHttp2FrameDecoderListener::OnDataStart(
+ const Http2FrameHeader& header) {
+ QUICHE_VLOG(1) << "OnDataStart: " << header;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnDataStart(header);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnDataPayload(const char* data,
+ size_t len) {
+ QUICHE_VLOG(1) << "OnDataPayload: len=" << len;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnDataPayload(data, len);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnDataEnd() {
+ QUICHE_VLOG(1) << "OnDataEnd";
+ if (wrapped_ != nullptr) {
+ wrapped_->OnDataEnd();
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnHeadersStart(
+ const Http2FrameHeader& header) {
+ QUICHE_VLOG(1) << "OnHeadersStart: " << header;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnHeadersStart(header);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnHeadersPriority(
+ const Http2PriorityFields& priority) {
+ QUICHE_VLOG(1) << "OnHeadersPriority: " << priority;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnHeadersPriority(priority);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnHpackFragment(const char* data,
+ size_t len) {
+ QUICHE_VLOG(1) << "OnHpackFragment: len=" << len;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnHpackFragment(data, len);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnHeadersEnd() {
+ QUICHE_VLOG(1) << "OnHeadersEnd";
+ if (wrapped_ != nullptr) {
+ wrapped_->OnHeadersEnd();
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPriorityFrame(
+ const Http2FrameHeader& header, const Http2PriorityFields& priority) {
+ QUICHE_VLOG(1) << "OnPriorityFrame: " << header << "; priority: " << priority;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnPriorityFrame(header, priority);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnContinuationStart(
+ const Http2FrameHeader& header) {
+ QUICHE_VLOG(1) << "OnContinuationStart: " << header;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnContinuationStart(header);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnContinuationEnd() {
+ QUICHE_VLOG(1) << "OnContinuationEnd";
+ if (wrapped_ != nullptr) {
+ wrapped_->OnContinuationEnd();
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPadLength(size_t trailing_length) {
+ QUICHE_VLOG(1) << "OnPadLength: trailing_length=" << trailing_length;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnPadLength(trailing_length);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPadding(const char* padding,
+ size_t skipped_length) {
+ QUICHE_VLOG(1) << "OnPadding: skipped_length=" << skipped_length;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnPadding(padding, skipped_length);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnRstStream(
+ const Http2FrameHeader& header, Http2ErrorCode error_code) {
+ QUICHE_VLOG(1) << "OnRstStream: " << header << "; code=" << error_code;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnRstStream(header, error_code);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnSettingsStart(
+ const Http2FrameHeader& header) {
+ QUICHE_VLOG(1) << "OnSettingsStart: " << header;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnSettingsStart(header);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnSetting(
+ const Http2SettingFields& setting_fields) {
+ QUICHE_VLOG(1) << "OnSetting: " << setting_fields;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnSetting(setting_fields);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnSettingsEnd() {
+ QUICHE_VLOG(1) << "OnSettingsEnd";
+ if (wrapped_ != nullptr) {
+ wrapped_->OnSettingsEnd();
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnSettingsAck(
+ const Http2FrameHeader& header) {
+ QUICHE_VLOG(1) << "OnSettingsAck: " << header;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnSettingsAck(header);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPushPromiseStart(
+ const Http2FrameHeader& header, const Http2PushPromiseFields& promise,
+ size_t total_padding_length) {
+ QUICHE_VLOG(1) << "OnPushPromiseStart: " << header << "; promise: " << promise
+ << "; total_padding_length: " << total_padding_length;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnPushPromiseStart(header, promise, total_padding_length);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPushPromiseEnd() {
+ QUICHE_VLOG(1) << "OnPushPromiseEnd";
+ if (wrapped_ != nullptr) {
+ wrapped_->OnPushPromiseEnd();
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPing(const Http2FrameHeader& header,
+ const Http2PingFields& ping) {
+ QUICHE_VLOG(1) << "OnPing: " << header << "; ping: " << ping;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnPing(header, ping);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPingAck(const Http2FrameHeader& header,
+ const Http2PingFields& ping) {
+ QUICHE_VLOG(1) << "OnPingAck: " << header << "; ping: " << ping;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnPingAck(header, ping);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnGoAwayStart(
+ const Http2FrameHeader& header, const Http2GoAwayFields& goaway) {
+ QUICHE_VLOG(1) << "OnGoAwayStart: " << header << "; goaway: " << goaway;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnGoAwayStart(header, goaway);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnGoAwayOpaqueData(const char* data,
+ size_t len) {
+ QUICHE_VLOG(1) << "OnGoAwayOpaqueData: len=" << len;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnGoAwayOpaqueData(data, len);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnGoAwayEnd() {
+ QUICHE_VLOG(1) << "OnGoAwayEnd";
+ if (wrapped_ != nullptr) {
+ wrapped_->OnGoAwayEnd();
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnWindowUpdate(
+ const Http2FrameHeader& header, uint32_t increment) {
+ QUICHE_VLOG(1) << "OnWindowUpdate: " << header << "; increment=" << increment;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnWindowUpdate(header, increment);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnAltSvcStart(
+ const Http2FrameHeader& header, size_t origin_length, size_t value_length) {
+ QUICHE_VLOG(1) << "OnAltSvcStart: " << header
+ << "; origin_length: " << origin_length
+ << "; value_length: " << value_length;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnAltSvcStart(header, origin_length, value_length);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnAltSvcOriginData(const char* data,
+ size_t len) {
+ QUICHE_VLOG(1) << "OnAltSvcOriginData: len=" << len;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnAltSvcOriginData(data, len);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnAltSvcValueData(const char* data,
+ size_t len) {
+ QUICHE_VLOG(1) << "OnAltSvcValueData: len=" << len;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnAltSvcValueData(data, len);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnAltSvcEnd() {
+ QUICHE_VLOG(1) << "OnAltSvcEnd";
+ if (wrapped_ != nullptr) {
+ wrapped_->OnAltSvcEnd();
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPriorityUpdateStart(
+ const Http2FrameHeader& header,
+ const Http2PriorityUpdateFields& priority_update) {
+ QUICHE_VLOG(1) << "OnPriorityUpdateStart";
+ if (wrapped_ != nullptr) {
+ wrapped_->OnPriorityUpdateStart(header, priority_update);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPriorityUpdatePayload(const char* data,
+ size_t len) {
+ QUICHE_VLOG(1) << "OnPriorityUpdatePayload";
+ if (wrapped_ != nullptr) {
+ wrapped_->OnPriorityUpdatePayload(data, len);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPriorityUpdateEnd() {
+ QUICHE_VLOG(1) << "OnPriorityUpdateEnd";
+ if (wrapped_ != nullptr) {
+ wrapped_->OnPriorityUpdateEnd();
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnUnknownStart(
+ const Http2FrameHeader& header) {
+ QUICHE_VLOG(1) << "OnUnknownStart: " << header;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnUnknownStart(header);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnUnknownPayload(const char* data,
+ size_t len) {
+ QUICHE_VLOG(1) << "OnUnknownPayload: len=" << len;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnUnknownPayload(data, len);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnUnknownEnd() {
+ QUICHE_VLOG(1) << "OnUnknownEnd";
+ if (wrapped_ != nullptr) {
+ wrapped_->OnUnknownEnd();
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPaddingTooLong(
+ const Http2FrameHeader& header, size_t missing_length) {
+ QUICHE_VLOG(1) << "OnPaddingTooLong: " << header
+ << "; missing_length: " << missing_length;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnPaddingTooLong(header, missing_length);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnFrameSizeError(
+ const Http2FrameHeader& header) {
+ QUICHE_VLOG(1) << "OnFrameSizeError: " << header;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnFrameSizeError(header);
+ }
+}
+
+} // namespace http2
diff --git a/quiche/http2/test_tools/http2_frame_decoder_listener_test_util.h b/quiche/http2/test_tools/http2_frame_decoder_listener_test_util.h
new file mode 100644
index 0000000..db341ce
--- /dev/null
+++ b/quiche/http2/test_tools/http2_frame_decoder_listener_test_util.h
@@ -0,0 +1,154 @@
+// 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_TEST_TOOLS_HTTP2_FRAME_DECODER_LISTENER_TEST_UTIL_H_
+#define QUICHE_HTTP2_TEST_TOOLS_HTTP2_FRAME_DECODER_LISTENER_TEST_UTIL_H_
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "quiche/http2/decoder/http2_frame_decoder_listener.h"
+#include "quiche/http2/http2_constants.h"
+#include "quiche/http2/http2_structures.h"
+#include "quiche/common/platform/api/quiche_export.h"
+
+namespace http2 {
+
+// Fail if any of the methods are called. Allows a test to override only the
+// expected calls.
+class QUICHE_NO_EXPORT FailingHttp2FrameDecoderListener
+ : public Http2FrameDecoderListener {
+ public:
+ FailingHttp2FrameDecoderListener();
+ ~FailingHttp2FrameDecoderListener() override;
+
+ // TODO(jamessynge): Remove OnFrameHeader once done with supporting
+ // SpdyFramer's exact states.
+ bool OnFrameHeader(const Http2FrameHeader& header) override;
+ void OnDataStart(const Http2FrameHeader& header) override;
+ void OnDataPayload(const char* data, size_t len) override;
+ void OnDataEnd() override;
+ void OnHeadersStart(const Http2FrameHeader& header) override;
+ void OnHeadersPriority(const Http2PriorityFields& priority) override;
+ void OnHpackFragment(const char* data, size_t len) override;
+ void OnHeadersEnd() override;
+ void OnPriorityFrame(const Http2FrameHeader& header,
+ const Http2PriorityFields& priority) override;
+ void OnContinuationStart(const Http2FrameHeader& header) override;
+ void OnContinuationEnd() override;
+ void OnPadLength(size_t trailing_length) override;
+ void OnPadding(const char* padding, size_t skipped_length) override;
+ void OnRstStream(const Http2FrameHeader& header,
+ Http2ErrorCode error_code) override;
+ void OnSettingsStart(const Http2FrameHeader& header) override;
+ void OnSetting(const Http2SettingFields& setting_fields) override;
+ void OnSettingsEnd() override;
+ void OnSettingsAck(const Http2FrameHeader& header) override;
+ void OnPushPromiseStart(const Http2FrameHeader& header,
+ const Http2PushPromiseFields& promise,
+ size_t total_padding_length) override;
+ void OnPushPromiseEnd() override;
+ void OnPing(const Http2FrameHeader& header,
+ const Http2PingFields& ping) override;
+ void OnPingAck(const Http2FrameHeader& header,
+ const Http2PingFields& ping) override;
+ void OnGoAwayStart(const Http2FrameHeader& header,
+ const Http2GoAwayFields& goaway) override;
+ void OnGoAwayOpaqueData(const char* data, size_t len) override;
+ void OnGoAwayEnd() override;
+ void OnWindowUpdate(const Http2FrameHeader& header,
+ uint32_t increment) override;
+ void OnAltSvcStart(const Http2FrameHeader& header, size_t origin_length,
+ size_t value_length) override;
+ void OnAltSvcOriginData(const char* data, size_t len) override;
+ void OnAltSvcValueData(const char* data, size_t len) override;
+ void OnAltSvcEnd() override;
+ void OnPriorityUpdateStart(
+ const Http2FrameHeader& header,
+ const Http2PriorityUpdateFields& priority_update) override;
+ void OnPriorityUpdatePayload(const char* data, size_t len) override;
+ void OnPriorityUpdateEnd() override;
+ void OnUnknownStart(const Http2FrameHeader& header) override;
+ void OnUnknownPayload(const char* data, size_t len) override;
+ void OnUnknownEnd() override;
+ void OnPaddingTooLong(const Http2FrameHeader& header,
+ size_t missing_length) override;
+ void OnFrameSizeError(const Http2FrameHeader& header) override;
+
+ private:
+ void EnsureNotAbstract() { FailingHttp2FrameDecoderListener instance; }
+};
+
+// QUICHE_VLOG's all the calls it receives, and forwards those calls to an
+// optional listener.
+class QUICHE_NO_EXPORT LoggingHttp2FrameDecoderListener
+ : public Http2FrameDecoderListener {
+ public:
+ LoggingHttp2FrameDecoderListener();
+ explicit LoggingHttp2FrameDecoderListener(Http2FrameDecoderListener* wrapped);
+ ~LoggingHttp2FrameDecoderListener() override;
+
+ // TODO(jamessynge): Remove OnFrameHeader once done with supporting
+ // SpdyFramer's exact states.
+ bool OnFrameHeader(const Http2FrameHeader& header) override;
+ void OnDataStart(const Http2FrameHeader& header) override;
+ void OnDataPayload(const char* data, size_t len) override;
+ void OnDataEnd() override;
+ void OnHeadersStart(const Http2FrameHeader& header) override;
+ void OnHeadersPriority(const Http2PriorityFields& priority) override;
+ void OnHpackFragment(const char* data, size_t len) override;
+ void OnHeadersEnd() override;
+ void OnPriorityFrame(const Http2FrameHeader& header,
+ const Http2PriorityFields& priority) override;
+ void OnContinuationStart(const Http2FrameHeader& header) override;
+ void OnContinuationEnd() override;
+ void OnPadLength(size_t trailing_length) override;
+ void OnPadding(const char* padding, size_t skipped_length) override;
+ void OnRstStream(const Http2FrameHeader& header,
+ Http2ErrorCode error_code) override;
+ void OnSettingsStart(const Http2FrameHeader& header) override;
+ void OnSetting(const Http2SettingFields& setting_fields) override;
+ void OnSettingsEnd() override;
+ void OnSettingsAck(const Http2FrameHeader& header) override;
+ void OnPushPromiseStart(const Http2FrameHeader& header,
+ const Http2PushPromiseFields& promise,
+ size_t total_padding_length) override;
+ void OnPushPromiseEnd() override;
+ void OnPing(const Http2FrameHeader& header,
+ const Http2PingFields& ping) override;
+ void OnPingAck(const Http2FrameHeader& header,
+ const Http2PingFields& ping) override;
+ void OnGoAwayStart(const Http2FrameHeader& header,
+ const Http2GoAwayFields& goaway) override;
+ void OnGoAwayOpaqueData(const char* data, size_t len) override;
+ void OnGoAwayEnd() override;
+ void OnWindowUpdate(const Http2FrameHeader& header,
+ uint32_t increment) override;
+ void OnAltSvcStart(const Http2FrameHeader& header, size_t origin_length,
+ size_t value_length) override;
+ void OnAltSvcOriginData(const char* data, size_t len) override;
+ void OnAltSvcValueData(const char* data, size_t len) override;
+ void OnAltSvcEnd() override;
+ void OnPriorityUpdateStart(
+ const Http2FrameHeader& header,
+ const Http2PriorityUpdateFields& priority_update) override;
+ void OnPriorityUpdatePayload(const char* data, size_t len) override;
+ void OnPriorityUpdateEnd() override;
+ void OnUnknownStart(const Http2FrameHeader& header) override;
+ void OnUnknownPayload(const char* data, size_t len) override;
+ void OnUnknownEnd() override;
+ void OnPaddingTooLong(const Http2FrameHeader& header,
+ size_t missing_length) override;
+ void OnFrameSizeError(const Http2FrameHeader& header) override;
+
+ private:
+ void EnsureNotAbstract() { LoggingHttp2FrameDecoderListener instance; }
+
+ Http2FrameDecoderListener* wrapped_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_TEST_TOOLS_HTTP2_FRAME_DECODER_LISTENER_TEST_UTIL_H_
diff --git a/quiche/http2/test_tools/http2_structure_decoder_test_util.cc b/quiche/http2/test_tools/http2_structure_decoder_test_util.cc
new file mode 100644
index 0000000..214fd85
--- /dev/null
+++ b/quiche/http2/test_tools/http2_structure_decoder_test_util.cc
@@ -0,0 +1,22 @@
+// Copyright 2017 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 "quiche/http2/test_tools/http2_structure_decoder_test_util.h"
+
+#include <cstddef>
+
+namespace http2 {
+namespace test {
+
+// static
+void Http2StructureDecoderPeer::Randomize(Http2StructureDecoder* p,
+ Http2Random* rng) {
+ p->offset_ = rng->Rand32();
+ for (size_t i = 0; i < sizeof p->buffer_; ++i) {
+ p->buffer_[i] = rng->Rand8();
+ }
+}
+
+} // namespace test
+} // namespace http2
diff --git a/quiche/http2/test_tools/http2_structure_decoder_test_util.h b/quiche/http2/test_tools/http2_structure_decoder_test_util.h
new file mode 100644
index 0000000..ad367c3
--- /dev/null
+++ b/quiche/http2/test_tools/http2_structure_decoder_test_util.h
@@ -0,0 +1,24 @@
+// 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_TEST_TOOLS_HTTP2_STRUCTURE_DECODER_TEST_UTIL_H_
+#define QUICHE_HTTP2_TEST_TOOLS_HTTP2_STRUCTURE_DECODER_TEST_UTIL_H_
+
+#include "quiche/http2/decoder/http2_structure_decoder.h"
+#include "quiche/http2/test_tools/http2_random.h"
+#include "quiche/common/platform/api/quiche_export.h"
+
+namespace http2 {
+namespace test {
+
+class QUICHE_NO_EXPORT Http2StructureDecoderPeer {
+ public:
+ // Overwrite the Http2StructureDecoder instance with random values.
+ static void Randomize(Http2StructureDecoder* p, Http2Random* rng);
+};
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_TEST_TOOLS_HTTP2_STRUCTURE_DECODER_TEST_UTIL_H_
diff --git a/quiche/http2/test_tools/http2_structures_test_util.cc b/quiche/http2/test_tools/http2_structures_test_util.cc
new file mode 100644
index 0000000..09bf1ee
--- /dev/null
+++ b/quiche/http2/test_tools/http2_structures_test_util.cc
@@ -0,0 +1,112 @@
+// 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 "quiche/http2/test_tools/http2_structures_test_util.h"
+
+#include <cstdint>
+
+#include "quiche/http2/http2_constants.h"
+#include "quiche/http2/http2_structures.h"
+#include "quiche/http2/test_tools/http2_constants_test_util.h"
+#include "quiche/http2/test_tools/http2_random.h"
+
+namespace http2 {
+namespace test {
+
+void Randomize(Http2FrameHeader* out, Http2Random* rng) {
+ out->payload_length = rng->Rand32() & 0xffffff;
+ out->type = static_cast<Http2FrameType>(rng->Rand8());
+ out->flags = static_cast<Http2FrameFlag>(rng->Rand8());
+ out->stream_id = rng->Rand32() & StreamIdMask();
+}
+void Randomize(Http2PriorityFields* out, Http2Random* rng) {
+ out->stream_dependency = rng->Rand32() & StreamIdMask();
+ out->weight = rng->Rand8() + 1;
+ out->is_exclusive = rng->OneIn(2);
+}
+void Randomize(Http2RstStreamFields* out, Http2Random* rng) {
+ out->error_code = static_cast<Http2ErrorCode>(rng->Rand32());
+}
+void Randomize(Http2SettingFields* out, Http2Random* rng) {
+ out->parameter = static_cast<Http2SettingsParameter>(rng->Rand16());
+ out->value = rng->Rand32();
+}
+void Randomize(Http2PushPromiseFields* out, Http2Random* rng) {
+ out->promised_stream_id = rng->Rand32() & StreamIdMask();
+}
+void Randomize(Http2PingFields* out, Http2Random* rng) {
+ for (int ndx = 0; ndx < 8; ++ndx) {
+ out->opaque_bytes[ndx] = rng->Rand8();
+ }
+}
+void Randomize(Http2GoAwayFields* out, Http2Random* rng) {
+ out->last_stream_id = rng->Rand32() & StreamIdMask();
+ out->error_code = static_cast<Http2ErrorCode>(rng->Rand32());
+}
+void Randomize(Http2WindowUpdateFields* out, Http2Random* rng) {
+ out->window_size_increment = rng->Rand32() & 0x7fffffff;
+}
+void Randomize(Http2AltSvcFields* out, Http2Random* rng) {
+ out->origin_length = rng->Rand16();
+}
+void Randomize(Http2PriorityUpdateFields* out, Http2Random* rng) {
+ out->prioritized_stream_id = rng->Rand32() & StreamIdMask();
+}
+
+void ScrubFlagsOfHeader(Http2FrameHeader* header) {
+ uint8_t invalid_mask = InvalidFlagMaskForFrameType(header->type);
+ uint8_t keep_mask = ~invalid_mask;
+ header->RetainFlags(keep_mask);
+}
+
+bool FrameIsPadded(const Http2FrameHeader& header) {
+ switch (header.type) {
+ case Http2FrameType::DATA:
+ case Http2FrameType::HEADERS:
+ case Http2FrameType::PUSH_PROMISE:
+ return header.IsPadded();
+ default:
+ return false;
+ }
+}
+
+bool FrameHasPriority(const Http2FrameHeader& header) {
+ switch (header.type) {
+ case Http2FrameType::HEADERS:
+ return header.HasPriority();
+ case Http2FrameType::PRIORITY:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool FrameCanHavePayload(const Http2FrameHeader& header) {
+ switch (header.type) {
+ case Http2FrameType::DATA:
+ case Http2FrameType::HEADERS:
+ case Http2FrameType::PUSH_PROMISE:
+ case Http2FrameType::CONTINUATION:
+ case Http2FrameType::PING:
+ case Http2FrameType::GOAWAY:
+ case Http2FrameType::ALTSVC:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool FrameCanHaveHpackPayload(const Http2FrameHeader& header) {
+ switch (header.type) {
+ case Http2FrameType::HEADERS:
+ case Http2FrameType::PUSH_PROMISE:
+ case Http2FrameType::CONTINUATION:
+ return true;
+ default:
+ return false;
+ }
+}
+
+} // namespace test
+} // namespace http2
diff --git a/quiche/http2/test_tools/http2_structures_test_util.h b/quiche/http2/test_tools/http2_structures_test_util.h
new file mode 100644
index 0000000..5a5353e
--- /dev/null
+++ b/quiche/http2/test_tools/http2_structures_test_util.h
@@ -0,0 +1,61 @@
+// 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_TEST_TOOLS_HTTP2_STRUCTURES_TEST_UTIL_H_
+#define QUICHE_HTTP2_TEST_TOOLS_HTTP2_STRUCTURES_TEST_UTIL_H_
+
+#include <string>
+
+#include "quiche/http2/http2_structures.h"
+#include "quiche/http2/test_tools/http2_frame_builder.h"
+#include "quiche/http2/test_tools/http2_random.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace test {
+
+template <class S>
+std::string SerializeStructure(const S& s) {
+ Http2FrameBuilder fb;
+ fb.Append(s);
+ EXPECT_EQ(S::EncodedSize(), fb.size());
+ return fb.buffer();
+}
+
+// Randomize the members of out, in a manner that yields encodeable contents
+// (e.g. a "uint24" field has only the low 24 bits set).
+void Randomize(Http2FrameHeader* out, Http2Random* rng);
+void Randomize(Http2PriorityFields* out, Http2Random* rng);
+void Randomize(Http2RstStreamFields* out, Http2Random* rng);
+void Randomize(Http2SettingFields* out, Http2Random* rng);
+void Randomize(Http2PushPromiseFields* out, Http2Random* rng);
+void Randomize(Http2PingFields* out, Http2Random* rng);
+void Randomize(Http2GoAwayFields* out, Http2Random* rng);
+void Randomize(Http2WindowUpdateFields* out, Http2Random* rng);
+void Randomize(Http2AltSvcFields* out, Http2Random* rng);
+void Randomize(Http2PriorityUpdateFields* out, Http2Random* rng);
+
+// Clear bits of header->flags that are known to be invalid for the
+// type. For unknown frame types, no change is made.
+void ScrubFlagsOfHeader(Http2FrameHeader* header);
+
+// Is the frame with this header padded? Only true for known/supported frame
+// types.
+bool FrameIsPadded(const Http2FrameHeader& header);
+
+// Does the frame with this header have Http2PriorityFields?
+bool FrameHasPriority(const Http2FrameHeader& header);
+
+// Does the frame with this header have a variable length payload (including
+// empty) payload (e.g. DATA or HEADERS)? Really a test of the frame type.
+bool FrameCanHavePayload(const Http2FrameHeader& header);
+
+// Does the frame with this header have a variable length HPACK payload
+// (including empty) payload (e.g. HEADERS)? Really a test of the frame type.
+bool FrameCanHaveHpackPayload(const Http2FrameHeader& header);
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_TEST_TOOLS_HTTP2_STRUCTURES_TEST_UTIL_H_
diff --git a/quiche/http2/test_tools/payload_decoder_base_test_util.cc b/quiche/http2/test_tools/payload_decoder_base_test_util.cc
new file mode 100644
index 0000000..5467658
--- /dev/null
+++ b/quiche/http2/test_tools/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 "quiche/http2/test_tools/payload_decoder_base_test_util.h"
+
+#include "quiche/http2/test_tools/frame_decoder_state_test_util.h"
+#include "quiche/http2/test_tools/http2_structures_test_util.h"
+#include "quiche/common/platform/api/quiche_test.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) {
+ QUICHE_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.
+ frame_decoder_state_ = std::make_unique<FrameDecoderState>();
+ 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_.get());
+ 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) {
+ QUICHE_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(
+ absl::string_view 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 = absl::string_view(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/quiche/http2/test_tools/payload_decoder_base_test_util.h b/quiche/http2/test_tools/payload_decoder_base_test_util.h
new file mode 100644
index 0000000..5f65982
--- /dev/null
+++ b/quiche/http2/test_tools/payload_decoder_base_test_util.h
@@ -0,0 +1,441 @@
+// 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_TEST_TOOLS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_
+#define QUICHE_HTTP2_TEST_TOOLS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_
+
+// Base class for testing concrete payload decoder classes.
+
+#include <stddef.h>
+
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "quiche/http2/decoder/decode_buffer.h"
+#include "quiche/http2/decoder/decode_status.h"
+#include "quiche/http2/decoder/frame_decoder_state.h"
+#include "quiche/http2/decoder/http2_frame_decoder_listener.h"
+#include "quiche/http2/http2_constants.h"
+#include "quiche/http2/http2_structures.h"
+#include "quiche/http2/test_tools/frame_parts.h"
+#include "quiche/http2/test_tools/http2_constants_test_util.h"
+#include "quiche/http2/test_tools/http2_frame_builder.h"
+#include "quiche/http2/test_tools/random_decoder_test.h"
+#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/platform/api/quiche_test_helpers.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 QUICHE_NO_EXPORT 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) {
+ QUICHE_VLOG(2) << "set_frame_header: " << frame_header_;
+ }
+ frame_header_ = header;
+ frame_header_is_set_ = true;
+ }
+
+ FrameDecoderState* mutable_state() { return frame_decoder_state_.get(); }
+
+ // 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(
+ absl::string_view 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_;
+ std::unique_ptr<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 QUICHE_NO_EXPORT 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 {
+ payload_decoder_ = std::make_unique<Decoder>();
+ }
+
+ 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 {
+ QUICHE_DVLOG(2) << "StartDecodingPayload, db->Remaining="
+ << db->Remaining();
+ return payload_decoder_->StartDecodingPayload(mutable_state(), db);
+ }
+
+ // Resume decoding the payload.
+ DecodeStatus ResumeDecodingPayload(DecodeBuffer* db) override {
+ QUICHE_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(
+ absl::string_view payload, const FrameParts& expected) {
+ auto validator = [&expected, this]() -> AssertionResult {
+ VERIFY_FALSE(listener_.IsInProgress());
+ VERIFY_EQ(1u, listener_.size());
+ return 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(
+ absl::string_view 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 {
+ QUICHE_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);
+ };
+ return 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, absl::string_view 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;
+ }
+ QUICHE_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, absl::string_view 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);
+ return VerifyDetectsMultipleFrameSizeErrors(
+ required_flags, unpadded_payload, approve_size, 0);
+ }
+
+ Listener listener_;
+ std::unique_ptr<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 QUICHE_NO_EXPORT 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()) {
+ QUICHE_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(
+ absl::string_view 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&,
+ 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();
+ };
+ return 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());
+ QUICHE_VLOG(1) << "fb.size=" << fb.size();
+ // Pick a random length for the payload that is shorter than neccesary.
+ payload_length = Random().Uniform(fb.size());
+ }
+
+ QUICHE_VLOG(1) << "payload_length=" << payload_length;
+ std::string 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;
+ QUICHE_VLOG(1) << "missing_length=" << missing_length;
+
+ const Http2FrameHeader header(payload_length, DecoderPeer::FrameType(),
+ flags, RandStreamId());
+ return 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_TEST_TOOLS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_