diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2022-05-17 17:24:03 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2022-06-22 07:51:41 +0000 |
commit | 774f54339e5db91f785733232d3950366db65d07 (patch) | |
tree | 068e1b47bd1af94d77094ed12b604a6b83d9c22a /chromium/net/third_party/quiche/src/quiche/http2/hpack | |
parent | f7eaed5286974984ba5f9e3189d8f49d03e99f81 (diff) | |
download | qtwebengine-chromium-774f54339e5db91f785733232d3950366db65d07.tar.gz |
BASELINE: Update Chromium to 102.0.5005.57
Change-Id: I885f714bb40ee724c28f94ca6bd8dbdb39915158
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/net/third_party/quiche/src/quiche/http2/hpack')
68 files changed, 11279 insertions, 0 deletions
diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_block_collector.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_block_collector.cc new file mode 100644 index 00000000000..180dd24e2a5 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_block_collector.cc @@ -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. + +#include "quiche/http2/hpack/decoder/hpack_block_collector.h" + +#include <algorithm> +#include <memory> + +#include "quiche/http2/platform/api/http2_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()); + HTTP2_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/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_block_collector.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_block_collector.h new file mode 100644 index 00000000000..be24110f262 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_block_collector.h @@ -0,0 +1,129 @@ +// 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_HPACK_DECODER_HPACK_BLOCK_COLLECTOR_H_ +#define QUICHE_HTTP2_HPACK_DECODER_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_collector.h" +#include "quiche/http2/hpack/decoder/hpack_entry_decoder_listener.h" +#include "quiche/http2/hpack/http2_hpack_constants.h" +#include "quiche/http2/hpack/tools/hpack_block_builder.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_HPACK_DECODER_HPACK_BLOCK_COLLECTOR_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_block_decoder.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_block_decoder.cc new file mode 100644 index 00000000000..8c8d4c064bd --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_block_decoder.cc @@ -0,0 +1,66 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/http2/hpack/decoder/hpack_block_decoder.h" + +#include <cstdint> + +#include "absl/strings/str_cat.h" +#include "quiche/http2/platform/api/http2_flag_utils.h" +#include "quiche/http2/platform/api/http2_flags.h" +#include "quiche/http2/platform/api/http2_logging.h" + +namespace http2 { + +DecodeStatus HpackBlockDecoder::Decode(DecodeBuffer* db) { + if (!before_entry_) { + HTTP2_DVLOG(2) << "HpackBlockDecoder::Decode resume entry, db->Remaining=" + << db->Remaining(); + DecodeStatus status = entry_decoder_.Resume(db, listener_); + switch (status) { + case DecodeStatus::kDecodeDone: + before_entry_ = true; + break; + case DecodeStatus::kDecodeInProgress: + QUICHE_DCHECK_EQ(0u, db->Remaining()); + return DecodeStatus::kDecodeInProgress; + case DecodeStatus::kDecodeError: + HTTP2_CODE_COUNT_N(decompress_failure_3, 1, 23); + return DecodeStatus::kDecodeError; + } + } + QUICHE_DCHECK(before_entry_); + while (db->HasData()) { + HTTP2_DVLOG(2) << "HpackBlockDecoder::Decode start entry, db->Remaining=" + << db->Remaining(); + DecodeStatus status = entry_decoder_.Start(db, listener_); + switch (status) { + case DecodeStatus::kDecodeDone: + continue; + case DecodeStatus::kDecodeInProgress: + QUICHE_DCHECK_EQ(0u, db->Remaining()); + before_entry_ = false; + return DecodeStatus::kDecodeInProgress; + case DecodeStatus::kDecodeError: + HTTP2_CODE_COUNT_N(decompress_failure_3, 2, 23); + return DecodeStatus::kDecodeError; + } + QUICHE_DCHECK(false); + } + QUICHE_DCHECK(before_entry_); + return DecodeStatus::kDecodeDone; +} + +std::string HpackBlockDecoder::DebugString() const { + return absl::StrCat( + "HpackBlockDecoder(", entry_decoder_.DebugString(), ", listener@", + absl::Hex(reinterpret_cast<intptr_t>(listener_)), + (before_entry_ ? ", between entries)" : ", in an entry)")); +} + +std::ostream& operator<<(std::ostream& out, const HpackBlockDecoder& v) { + return out << v.DebugString(); +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_block_decoder.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_block_decoder.h new file mode 100644 index 00000000000..85ee8ff7126 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_block_decoder.h @@ -0,0 +1,69 @@ +// 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_HPACK_DECODER_HPACK_BLOCK_DECODER_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_BLOCK_DECODER_H_ + +// HpackBlockDecoder decodes an entire HPACK block (or the available portion +// thereof in the DecodeBuffer) into entries, but doesn't include HPACK static +// or dynamic table support, so table indices remain indices at this level. +// Reports the entries to an HpackEntryDecoderListener. + +#include <string> + +#include "quiche/http2/decoder/decode_buffer.h" +#include "quiche/http2/decoder/decode_status.h" +#include "quiche/http2/hpack/decoder/hpack_decoding_error.h" +#include "quiche/http2/hpack/decoder/hpack_entry_decoder.h" +#include "quiche/http2/hpack/decoder/hpack_entry_decoder_listener.h" +#include "quiche/http2/platform/api/http2_logging.h" +#include "quiche/common/platform/api/quiche_export.h" + +namespace http2 { + +class QUICHE_EXPORT_PRIVATE HpackBlockDecoder { + public: + explicit HpackBlockDecoder(HpackEntryDecoderListener* listener) + : listener_(listener) { + QUICHE_DCHECK_NE(listener_, nullptr); + } + ~HpackBlockDecoder() {} + + HpackBlockDecoder(const HpackBlockDecoder&) = delete; + HpackBlockDecoder& operator=(const HpackBlockDecoder&) = delete; + + // Prepares the decoder to start decoding a new HPACK block. Expected + // to be called from an implementation of Http2FrameDecoderListener's + // OnHeadersStart or OnPushPromiseStart methods. + void Reset() { + HTTP2_DVLOG(2) << "HpackBlockDecoder::Reset"; + before_entry_ = true; + } + + // Decode the fragment of the HPACK block contained in the decode buffer. + // Expected to be called from an implementation of Http2FrameDecoderListener's + // OnHpackFragment method. + DecodeStatus Decode(DecodeBuffer* db); + + // Is the decoding process between entries (i.e. would the next byte be the + // first byte of a new HPACK entry)? + bool before_entry() const { return before_entry_; } + + // Return error code after decoding error occurred in HpackEntryDecoder. + HpackDecodingError error() const { return entry_decoder_.error(); } + + std::string DebugString() const; + + private: + HpackEntryDecoder entry_decoder_; + HpackEntryDecoderListener* const listener_; + bool before_entry_ = true; +}; + +QUICHE_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const HpackBlockDecoder& v); + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_BLOCK_DECODER_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_block_decoder_test.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_block_decoder_test.cc new file mode 100644 index 00000000000..e8a1cb47620 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_block_decoder_test.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/hpack/decoder/hpack_block_decoder.h" + +// Tests of HpackBlockDecoder. + +#include <cstdint> +#include <string> + +#include "absl/strings/string_view.h" +#include "quiche/http2/decoder/decode_buffer.h" +#include "quiche/http2/hpack/decoder/hpack_block_collector.h" +#include "quiche/http2/hpack/http2_hpack_constants.h" +#include "quiche/http2/hpack/tools/hpack_block_builder.h" +#include "quiche/http2/hpack/tools/hpack_example.h" +#include "quiche/http2/test_tools/http2_random.h" +#include "quiche/http2/tools/random_decoder_test.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/common/platform/api/quiche_test_helpers.h" + +namespace http2 { +namespace test { +namespace { + +class HpackBlockDecoderTest : public RandomDecoderTest { + protected: + HpackBlockDecoderTest() : listener_(&collector_), decoder_(&listener_) { + stop_decode_on_done_ = false; + decoder_.Reset(); + // Make sure logging doesn't crash. Not examining the result. + std::ostringstream strm; + strm << decoder_; + } + + DecodeStatus StartDecoding(DecodeBuffer* db) override { + collector_.Clear(); + decoder_.Reset(); + return ResumeDecoding(db); + } + + DecodeStatus ResumeDecoding(DecodeBuffer* db) override { + DecodeStatus status = decoder_.Decode(db); + + // Make sure logging doesn't crash. Not examining the result. + std::ostringstream strm; + strm << decoder_; + + return status; + } + + AssertionResult DecodeAndValidateSeveralWays(DecodeBuffer* db, + const Validator& validator) { + bool return_non_zero_on_first = false; + return RandomDecoderTest::DecodeAndValidateSeveralWays( + db, return_non_zero_on_first, validator); + } + + AssertionResult DecodeAndValidateSeveralWays(const HpackBlockBuilder& hbb, + const Validator& validator) { + DecodeBuffer db(hbb.buffer()); + return DecodeAndValidateSeveralWays(&db, validator); + } + + AssertionResult DecodeHpackExampleAndValidateSeveralWays( + absl::string_view hpack_example, + Validator validator) { + std::string input = HpackExampleToStringOrDie(hpack_example); + DecodeBuffer db(input); + return DecodeAndValidateSeveralWays(&db, validator); + } + + uint8_t Rand8() { return Random().Rand8(); } + + std::string Rand8String() { return Random().RandString(Rand8()); } + + HpackBlockCollector collector_; + HpackEntryDecoderVLoggingListener listener_; + HpackBlockDecoder decoder_; +}; + +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.1 +TEST_F(HpackBlockDecoderTest, SpecExample_C_2_1) { + auto do_check = [this]() { + return collector_.ValidateSoleLiteralNameValueHeader( + HpackEntryType::kIndexedLiteralHeader, false, "custom-key", false, + "custom-header"); + }; + const char hpack_example[] = R"( + 40 | == Literal indexed == + 0a | Literal name (len = 10) + 6375 7374 6f6d 2d6b 6579 | custom-key + 0d | Literal value (len = 13) + 6375 7374 6f6d 2d68 6561 6465 72 | custom-header + | -> custom-key: + | custom-header + )"; + EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays( + hpack_example, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); +} + +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.2 +TEST_F(HpackBlockDecoderTest, SpecExample_C_2_2) { + auto do_check = [this]() { + return collector_.ValidateSoleLiteralValueHeader( + HpackEntryType::kUnindexedLiteralHeader, 4, false, "/sample/path"); + }; + const char hpack_example[] = R"( + 04 | == Literal not indexed == + | Indexed name (idx = 4) + | :path + 0c | Literal value (len = 12) + 2f73 616d 706c 652f 7061 7468 | /sample/path + | -> :path: /sample/path + )"; + EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays( + hpack_example, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); +} + +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.3 +TEST_F(HpackBlockDecoderTest, SpecExample_C_2_3) { + auto do_check = [this]() { + return collector_.ValidateSoleLiteralNameValueHeader( + HpackEntryType::kNeverIndexedLiteralHeader, false, "password", false, + "secret"); + }; + const char hpack_example[] = R"( + 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 + )"; + EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays( + hpack_example, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); +} + +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.4 +TEST_F(HpackBlockDecoderTest, SpecExample_C_2_4) { + auto do_check = [this]() { return collector_.ValidateSoleIndexedHeader(2); }; + const char hpack_example[] = R"( + 82 | == Indexed - Add == + | idx = 2 + | -> :method: GET + )"; + EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays( + hpack_example, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); +} +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.3.1 +TEST_F(HpackBlockDecoderTest, SpecExample_C_3_1) { + std::string example = R"( + 82 | == Indexed - Add == + | idx = 2 + | -> :method: GET + 86 | == Indexed - Add == + | idx = 6 + | -> :scheme: http + 84 | == Indexed - Add == + | idx = 4 + | -> :path: / + 41 | == Literal indexed == + | Indexed name (idx = 1) + | :authority + 0f | Literal value (len = 15) + 7777 772e 6578 616d 706c 652e 636f 6d | www.example.com + | -> :authority: + | www.example.com + )"; + HpackBlockCollector expected; + expected.ExpectIndexedHeader(2); + expected.ExpectIndexedHeader(6); + expected.ExpectIndexedHeader(4); + expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, + 1, false, "www.example.com"); + NoArgValidator do_check = [expected, this]() { + return collector_.VerifyEq(expected); + }; + EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays( + example, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); +} + +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.5.1 +TEST_F(HpackBlockDecoderTest, SpecExample_C_5_1) { + std::string example = R"( + 48 | == Literal indexed == + | Indexed name (idx = 8) + | :status + 03 | Literal value (len = 3) + 3330 32 | 302 + | -> :status: 302 + 58 | == Literal indexed == + | Indexed name (idx = 24) + | cache-control + 07 | Literal value (len = 7) + 7072 6976 6174 65 | private + | -> cache-control: private + 61 | == Literal indexed == + | Indexed name (idx = 33) + | date + 1d | Literal value (len = 29) + 4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013 + 2032 303a 3133 3a32 3120 474d 54 | 20:13:21 GMT + | -> date: Mon, 21 Oct 2013 + | 20:13:21 GMT + 6e | == Literal indexed == + | Indexed name (idx = 46) + | location + 17 | Literal value (len = 23) + 6874 7470 733a 2f2f 7777 772e 6578 616d | https://www.exam + 706c 652e 636f 6d | ple.com + | -> location: + | https://www.example.com + )"; + HpackBlockCollector expected; + expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, + 8, false, "302"); + expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, + 24, false, "private"); + expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, + 33, false, + "Mon, 21 Oct 2013 20:13:21 GMT"); + expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, + 46, false, "https://www.example.com"); + NoArgValidator do_check = [expected, this]() { + return collector_.VerifyEq(expected); + }; + EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays( + example, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); +} + +// Generate a bunch of HPACK block entries to expect, use those expectations +// to generate an HPACK block, then decode it and confirm it matches those +// expectations. Some of these are invalid (such as Indexed, with index=0), +// but well-formed, and the decoder doesn't check for validity, just +// well-formedness. That includes the validity of the strings not being checked, +// such as lower-case ascii for the names, and valid Huffman encodings. +TEST_F(HpackBlockDecoderTest, Computed) { + HpackBlockCollector expected; + expected.ExpectIndexedHeader(0); + expected.ExpectIndexedHeader(1); + expected.ExpectIndexedHeader(126); + expected.ExpectIndexedHeader(127); + expected.ExpectIndexedHeader(128); + expected.ExpectDynamicTableSizeUpdate(0); + expected.ExpectDynamicTableSizeUpdate(1); + expected.ExpectDynamicTableSizeUpdate(14); + expected.ExpectDynamicTableSizeUpdate(15); + expected.ExpectDynamicTableSizeUpdate(30); + expected.ExpectDynamicTableSizeUpdate(31); + expected.ExpectDynamicTableSizeUpdate(4095); + expected.ExpectDynamicTableSizeUpdate(4096); + expected.ExpectDynamicTableSizeUpdate(8192); + for (auto type : {HpackEntryType::kIndexedLiteralHeader, + HpackEntryType::kUnindexedLiteralHeader, + HpackEntryType::kNeverIndexedLiteralHeader}) { + for (bool value_huffman : {false, true}) { + // An entry with an index for the name. Ensure the name index + // is not zero by adding one to the Rand8() result. + expected.ExpectNameIndexAndLiteralValue(type, Rand8() + 1, value_huffman, + Rand8String()); + // And two entries with literal names, one plain, one huffman encoded. + expected.ExpectLiteralNameAndValue(type, false, Rand8String(), + value_huffman, Rand8String()); + expected.ExpectLiteralNameAndValue(type, true, Rand8String(), + value_huffman, Rand8String()); + } + } + // Shuffle the entries and serialize them to produce an HPACK block. + expected.ShuffleEntries(RandomPtr()); + HpackBlockBuilder hbb; + expected.AppendToHpackBlockBuilder(&hbb); + + NoArgValidator do_check = [expected, this]() { + return collector_.VerifyEq(expected); + }; + EXPECT_TRUE( + DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder.cc new file mode 100644 index 00000000000..77ad8bd543e --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder.cc @@ -0,0 +1,125 @@ +// 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/hpack/decoder/hpack_decoder.h" + +#include "quiche/http2/decoder/decode_status.h" +#include "quiche/http2/platform/api/http2_flag_utils.h" +#include "quiche/http2/platform/api/http2_flags.h" +#include "quiche/http2/platform/api/http2_logging.h" + +namespace http2 { + +HpackDecoder::HpackDecoder(HpackDecoderListener* listener, + size_t max_string_size) + : decoder_state_(listener), + entry_buffer_(&decoder_state_, max_string_size), + block_decoder_(&entry_buffer_), + error_(HpackDecodingError::kOk) {} + +HpackDecoder::~HpackDecoder() = default; + +void HpackDecoder::set_max_string_size_bytes(size_t max_string_size_bytes) { + entry_buffer_.set_max_string_size_bytes(max_string_size_bytes); +} + +void HpackDecoder::ApplyHeaderTableSizeSetting(uint32_t max_header_table_size) { + decoder_state_.ApplyHeaderTableSizeSetting(max_header_table_size); +} + +bool HpackDecoder::StartDecodingBlock() { + HTTP2_DVLOG(3) << "HpackDecoder::StartDecodingBlock, error_detected=" + << (DetectError() ? "true" : "false"); + if (DetectError()) { + return false; + } + // TODO(jamessynge): Eliminate Reset(), which shouldn't be necessary + // if there are no errors, and shouldn't be necessary with errors if + // we never resume decoding after an error has been detected. + block_decoder_.Reset(); + decoder_state_.OnHeaderBlockStart(); + return true; +} + +bool HpackDecoder::DecodeFragment(DecodeBuffer* db) { + HTTP2_DVLOG(3) << "HpackDecoder::DecodeFragment, error_detected=" + << (DetectError() ? "true" : "false") + << ", size=" << db->Remaining(); + if (DetectError()) { + HTTP2_CODE_COUNT_N(decompress_failure_3, 3, 23); + return false; + } + // Decode contents of db as an HPACK block fragment, forwards the decoded + // entries to entry_buffer_, which in turn forwards them to decode_state_, + // which finally forwards them to the HpackDecoderListener. + DecodeStatus status = block_decoder_.Decode(db); + if (status == DecodeStatus::kDecodeError) { + ReportError(block_decoder_.error(), ""); + HTTP2_CODE_COUNT_N(decompress_failure_3, 4, 23); + return false; + } else if (DetectError()) { + HTTP2_CODE_COUNT_N(decompress_failure_3, 5, 23); + return false; + } + // Should be positioned between entries iff decoding is complete. + QUICHE_DCHECK_EQ(block_decoder_.before_entry(), + status == DecodeStatus::kDecodeDone) + << status; + if (!block_decoder_.before_entry()) { + entry_buffer_.BufferStringsIfUnbuffered(); + } + return true; +} + +bool HpackDecoder::EndDecodingBlock() { + HTTP2_DVLOG(3) << "HpackDecoder::EndDecodingBlock, error_detected=" + << (DetectError() ? "true" : "false"); + if (DetectError()) { + HTTP2_CODE_COUNT_N(decompress_failure_3, 6, 23); + return false; + } + if (!block_decoder_.before_entry()) { + // The HPACK block ended in the middle of an entry. + ReportError(HpackDecodingError::kTruncatedBlock, ""); + HTTP2_CODE_COUNT_N(decompress_failure_3, 7, 23); + return false; + } + decoder_state_.OnHeaderBlockEnd(); + if (DetectError()) { + // HpackDecoderState will have reported the error. + HTTP2_CODE_COUNT_N(decompress_failure_3, 8, 23); + return false; + } + return true; +} + +bool HpackDecoder::DetectError() { + if (error_ != HpackDecodingError::kOk) { + return true; + } + + if (decoder_state_.error() != HpackDecodingError::kOk) { + HTTP2_DVLOG(2) << "Error detected in decoder_state_"; + HTTP2_CODE_COUNT_N(decompress_failure_3, 10, 23); + error_ = decoder_state_.error(); + detailed_error_ = decoder_state_.detailed_error(); + } + + return error_ != HpackDecodingError::kOk; +} + +void HpackDecoder::ReportError(HpackDecodingError error, + std::string detailed_error) { + HTTP2_DVLOG(3) << "HpackDecoder::ReportError is new=" + << (error_ == HpackDecodingError::kOk ? "true" : "false") + << ", error: " << HpackDecodingErrorToString(error); + if (error_ == HpackDecodingError::kOk) { + error_ = error; + detailed_error_ = detailed_error; + decoder_state_.listener()->OnHeaderErrorDetected( + HpackDecodingErrorToString(error)); + } +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder.h new file mode 100644 index 00000000000..393a6ca0c40 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder.h @@ -0,0 +1,132 @@ +// 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. + +#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_H_ + +// Decodes HPACK blocks, calls an HpackDecoderListener with the decoded header +// entries. Also notifies the listener of errors and of the boundaries of the +// HPACK blocks. + +// TODO(jamessynge): Add feature allowing an HpackEntryDecoderListener +// sub-class (and possibly others) to be passed in for counting events, +// so that deciding whether to count is not done by having lots of if +// statements, but instead by inserting an indirection only when needed. + +// TODO(jamessynge): Consider whether to return false from methods below +// when an error has been previously detected. It protects calling code +// from its failure to pay attention to previous errors, but should we +// spend time to do that? + +#include <stddef.h> + +#include <cstdint> + +#include "quiche/http2/decoder/decode_buffer.h" +#include "quiche/http2/hpack/decoder/hpack_block_decoder.h" +#include "quiche/http2/hpack/decoder/hpack_decoder_listener.h" +#include "quiche/http2/hpack/decoder/hpack_decoder_state.h" +#include "quiche/http2/hpack/decoder/hpack_decoder_tables.h" +#include "quiche/http2/hpack/decoder/hpack_decoding_error.h" +#include "quiche/http2/hpack/decoder/hpack_whole_entry_buffer.h" +#include "quiche/common/platform/api/quiche_export.h" + +namespace http2 { +namespace test { +class HpackDecoderPeer; +} // namespace test + +class QUICHE_EXPORT_PRIVATE HpackDecoder { + public: + HpackDecoder(HpackDecoderListener* listener, size_t max_string_size); + virtual ~HpackDecoder(); + + HpackDecoder(const HpackDecoder&) = delete; + HpackDecoder& operator=(const HpackDecoder&) = delete; + + // max_string_size specifies the maximum size of an on-the-wire string (name + // or value, plain or Huffman encoded) that will be accepted. See sections + // 5.1 and 5.2 of RFC 7541. This is a defense against OOM attacks; HTTP/2 + // allows a decoder to enforce any limit of the size of the header lists + // that it is willing to decode, including less than the MAX_HEADER_LIST_SIZE + // setting, a setting that is initially unlimited. For example, we might + // choose to send a MAX_HEADER_LIST_SIZE of 64KB, and to use that same value + // as the upper bound for individual strings. + void set_max_string_size_bytes(size_t max_string_size_bytes); + + // ApplyHeaderTableSizeSetting notifies this object that this endpoint has + // received a SETTINGS ACK frame acknowledging an earlier SETTINGS frame from + // this endpoint specifying a new value for SETTINGS_HEADER_TABLE_SIZE (the + // maximum size of the dynamic table that this endpoint will use to decode + // HPACK blocks). + // Because a SETTINGS frame can contain SETTINGS_HEADER_TABLE_SIZE values, + // the caller must keep track of those multiple changes, and make + // corresponding calls to this method. In particular, a call must be made + // with the lowest value acknowledged by the peer, and a call must be made + // with the final value acknowledged, in that order; additional calls may + // be made if additional values were sent. These calls must be made between + // decoding the SETTINGS ACK, and before the next HPACK block is decoded. + void ApplyHeaderTableSizeSetting(uint32_t max_header_table_size); + + // Returns the most recently applied value of SETTINGS_HEADER_TABLE_SIZE. + size_t GetCurrentHeaderTableSizeSetting() const { + return decoder_state_.GetCurrentHeaderTableSizeSetting(); + } + + // Prepares the decoder for decoding a new HPACK block, and announces this to + // its listener. Returns true if OK to continue with decoding, false if an + // error has been detected, which for StartDecodingBlock means the error was + // detected while decoding a previous HPACK block. + bool StartDecodingBlock(); + + // Decodes a fragment (some or all of the remainder) of an HPACK block, + // reporting header entries (name & value pairs) that it completely decodes + // in the process to the listener. Returns true successfully decoded, false if + // an error has been detected, either during decoding of the fragment, or + // prior to this call. + bool DecodeFragment(DecodeBuffer* db); + + // Completes the process of decoding an HPACK block: if the HPACK block was + // properly terminated, announces the end of the header list to the listener + // and returns true; else returns false. + bool EndDecodingBlock(); + + // If no error has been detected so far, query |decoder_state_| for errors and + // set |error_| if necessary. Returns true if an error has ever been + // detected. + bool DetectError(); + + size_t GetDynamicTableSize() const { + return decoder_state_.GetDynamicTableSize(); + } + + // Error code if an error has occurred, HpackDecodingError::kOk otherwise. + HpackDecodingError error() const { return error_; } + + std::string detailed_error() const { return detailed_error_; } + + private: + friend class test::HpackDecoderPeer; + + // Reports an error to the listener IF this is the first error detected. + void ReportError(HpackDecodingError error, std::string detailed_error); + + // The decompressor state, as defined by HPACK (i.e. the static and dynamic + // tables). + HpackDecoderState decoder_state_; + + // Assembles the various parts of a header entry into whole entries. + HpackWholeEntryBuffer entry_buffer_; + + // The decoder of HPACK blocks into entry parts, passed to entry_buffer_. + HpackBlockDecoder block_decoder_; + + // Error code if an error has occurred, HpackDecodingError::kOk otherwise. + HpackDecodingError error_; + std::string detailed_error_; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_listener.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_listener.cc new file mode 100644 index 00000000000..75a59695c27 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_listener.cc @@ -0,0 +1,29 @@ +// 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/hpack/decoder/hpack_decoder_listener.h" + +namespace http2 { + +HpackDecoderListener::HpackDecoderListener() = default; +HpackDecoderListener::~HpackDecoderListener() = default; + +HpackDecoderNoOpListener::HpackDecoderNoOpListener() = default; +HpackDecoderNoOpListener::~HpackDecoderNoOpListener() = default; + +void HpackDecoderNoOpListener::OnHeaderListStart() {} +void HpackDecoderNoOpListener::OnHeader(const std::string& /*name*/, + const std::string& /*value*/) {} +void HpackDecoderNoOpListener::OnHeaderListEnd() {} +void HpackDecoderNoOpListener::OnHeaderErrorDetected( + absl::string_view /*error_message*/) {} + +// static +HpackDecoderNoOpListener* HpackDecoderNoOpListener::NoOpListener() { + static HpackDecoderNoOpListener* static_instance = + new HpackDecoderNoOpListener(); + return static_instance; +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_listener.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_listener.h new file mode 100644 index 00000000000..bd87c629340 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_listener.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. + +// Defines HpackDecoderListener, the base class of listeners for HTTP header +// lists decoded from an HPACK block. + +#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_LISTENER_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_LISTENER_H_ + +#include "absl/strings/string_view.h" +#include "quiche/http2/hpack/http2_hpack_constants.h" +#include "quiche/common/platform/api/quiche_export.h" + +namespace http2 { + +class QUICHE_EXPORT_PRIVATE HpackDecoderListener { + public: + HpackDecoderListener(); + virtual ~HpackDecoderListener(); + + // OnHeaderListStart is called at the start of decoding an HPACK block into + // an HTTP/2 header list. Will only be called once per block, even if it + // extends into CONTINUATION frames. + virtual void OnHeaderListStart() = 0; + + // Called for each header name-value pair that is decoded, in the order they + // appear in the HPACK block. Multiple values for a given key will be emitted + // as multiple calls to OnHeader. + virtual void OnHeader(const std::string& name, const std::string& value) = 0; + + // OnHeaderListEnd is called after successfully decoding an HPACK block into + // an HTTP/2 header list. Will only be called once per block, even if it + // extends into CONTINUATION frames. + virtual void OnHeaderListEnd() = 0; + + // OnHeaderErrorDetected is called if an error is detected while decoding. + // error_message may be used in a GOAWAY frame as the Opaque Data. + virtual void OnHeaderErrorDetected(absl::string_view error_message) = 0; +}; + +// A no-op implementation of HpackDecoderListener, useful for ignoring +// callbacks once an error is detected. +class QUICHE_EXPORT_PRIVATE HpackDecoderNoOpListener + : public HpackDecoderListener { + public: + HpackDecoderNoOpListener(); + ~HpackDecoderNoOpListener() override; + + void OnHeaderListStart() override; + void OnHeader(const std::string& name, const std::string& value) override; + void OnHeaderListEnd() override; + void OnHeaderErrorDetected(absl::string_view error_message) override; + + // Returns a listener that ignores all the calls. + static HpackDecoderNoOpListener* NoOpListener(); +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_LISTENER_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_state.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_state.cc new file mode 100644 index 00000000000..c8672bb291f --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_state.cc @@ -0,0 +1,226 @@ +// 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/hpack/decoder/hpack_decoder_state.h" + +#include <utility> + +#include "quiche/http2/http2_constants.h" +#include "quiche/http2/platform/api/http2_logging.h" +#include "quiche/common/platform/api/quiche_logging.h" + +namespace http2 { +namespace { + +std::string ExtractString(HpackDecoderStringBuffer* string_buffer) { + if (string_buffer->IsBuffered()) { + return string_buffer->ReleaseString(); + } else { + auto result = std::string(string_buffer->str()); + string_buffer->Reset(); + return result; + } +} + +} // namespace + +HpackDecoderState::HpackDecoderState(HpackDecoderListener* listener) + : listener_(listener), + final_header_table_size_(Http2SettingsInfo::DefaultHeaderTableSize()), + lowest_header_table_size_(final_header_table_size_), + require_dynamic_table_size_update_(false), + allow_dynamic_table_size_update_(true), + saw_dynamic_table_size_update_(false), + error_(HpackDecodingError::kOk) { + QUICHE_CHECK(listener_); +} + +HpackDecoderState::~HpackDecoderState() = default; + +void HpackDecoderState::ApplyHeaderTableSizeSetting( + uint32_t header_table_size) { + HTTP2_DVLOG(2) << "HpackDecoderState::ApplyHeaderTableSizeSetting(" + << header_table_size << ")"; + QUICHE_DCHECK_LE(lowest_header_table_size_, final_header_table_size_); + if (header_table_size < lowest_header_table_size_) { + lowest_header_table_size_ = header_table_size; + } + final_header_table_size_ = header_table_size; + HTTP2_DVLOG(2) << "low water mark: " << lowest_header_table_size_; + HTTP2_DVLOG(2) << "final limit: " << final_header_table_size_; +} + +// Called to notify this object that we're starting to decode an HPACK block +// (e.g. a HEADERS or PUSH_PROMISE frame's header has been decoded). +void HpackDecoderState::OnHeaderBlockStart() { + HTTP2_DVLOG(2) << "HpackDecoderState::OnHeaderBlockStart"; + // This instance can't be reused after an error has been detected, as we must + // assume that the encoder and decoder compression states are no longer + // synchronized. + QUICHE_DCHECK(error_ == HpackDecodingError::kOk) + << HpackDecodingErrorToString(error_); + QUICHE_DCHECK_LE(lowest_header_table_size_, final_header_table_size_); + allow_dynamic_table_size_update_ = true; + saw_dynamic_table_size_update_ = false; + // If the peer has acknowledged a HEADER_TABLE_SIZE smaller than that which + // its HPACK encoder has been using, then the next HPACK block it sends MUST + // start with a Dynamic Table Size Update entry that is at least as low as + // lowest_header_table_size_. That may be followed by another as great as + // final_header_table_size_, if those are different. + require_dynamic_table_size_update_ = + (lowest_header_table_size_ < + decoder_tables_.current_header_table_size() || + final_header_table_size_ < decoder_tables_.header_table_size_limit()); + HTTP2_DVLOG(2) << "HpackDecoderState::OnHeaderListStart " + << "require_dynamic_table_size_update_=" + << require_dynamic_table_size_update_; + listener_->OnHeaderListStart(); +} + +void HpackDecoderState::OnIndexedHeader(size_t index) { + HTTP2_DVLOG(2) << "HpackDecoderState::OnIndexedHeader: " << index; + if (error_ != HpackDecodingError::kOk) { + return; + } + if (require_dynamic_table_size_update_) { + ReportError(HpackDecodingError::kMissingDynamicTableSizeUpdate, ""); + return; + } + allow_dynamic_table_size_update_ = false; + const HpackStringPair* entry = decoder_tables_.Lookup(index); + if (entry != nullptr) { + listener_->OnHeader(entry->name, entry->value); + } else { + ReportError(HpackDecodingError::kInvalidIndex, ""); + } +} + +void HpackDecoderState::OnNameIndexAndLiteralValue( + HpackEntryType entry_type, + size_t name_index, + HpackDecoderStringBuffer* value_buffer) { + HTTP2_DVLOG(2) << "HpackDecoderState::OnNameIndexAndLiteralValue " + << entry_type << ", " << name_index << ", " + << value_buffer->str(); + if (error_ != HpackDecodingError::kOk) { + return; + } + if (require_dynamic_table_size_update_) { + ReportError(HpackDecodingError::kMissingDynamicTableSizeUpdate, ""); + return; + } + allow_dynamic_table_size_update_ = false; + const HpackStringPair* entry = decoder_tables_.Lookup(name_index); + if (entry != nullptr) { + std::string value(ExtractString(value_buffer)); + listener_->OnHeader(entry->name, value); + if (entry_type == HpackEntryType::kIndexedLiteralHeader) { + decoder_tables_.Insert(entry->name, std::move(value)); + } + } else { + ReportError(HpackDecodingError::kInvalidNameIndex, ""); + } +} + +void HpackDecoderState::OnLiteralNameAndValue( + HpackEntryType entry_type, + HpackDecoderStringBuffer* name_buffer, + HpackDecoderStringBuffer* value_buffer) { + HTTP2_DVLOG(2) << "HpackDecoderState::OnLiteralNameAndValue " << entry_type + << ", " << name_buffer->str() << ", " << value_buffer->str(); + if (error_ != HpackDecodingError::kOk) { + return; + } + if (require_dynamic_table_size_update_) { + ReportError(HpackDecodingError::kMissingDynamicTableSizeUpdate, ""); + return; + } + allow_dynamic_table_size_update_ = false; + std::string name(ExtractString(name_buffer)); + std::string value(ExtractString(value_buffer)); + listener_->OnHeader(name, value); + if (entry_type == HpackEntryType::kIndexedLiteralHeader) { + decoder_tables_.Insert(std::move(name), std::move(value)); + } +} + +void HpackDecoderState::OnDynamicTableSizeUpdate(size_t size_limit) { + HTTP2_DVLOG(2) << "HpackDecoderState::OnDynamicTableSizeUpdate " << size_limit + << ", required=" + << (require_dynamic_table_size_update_ ? "true" : "false") + << ", allowed=" + << (allow_dynamic_table_size_update_ ? "true" : "false"); + if (error_ != HpackDecodingError::kOk) { + return; + } + QUICHE_DCHECK_LE(lowest_header_table_size_, final_header_table_size_); + if (!allow_dynamic_table_size_update_) { + // At most two dynamic table size updates allowed at the start, and not + // after a header. + ReportError(HpackDecodingError::kDynamicTableSizeUpdateNotAllowed, ""); + return; + } + if (require_dynamic_table_size_update_) { + // The new size must not be greater than the low water mark. + if (size_limit > lowest_header_table_size_) { + ReportError( + HpackDecodingError::kInitialDynamicTableSizeUpdateIsAboveLowWaterMark, + ""); + return; + } + require_dynamic_table_size_update_ = false; + } else if (size_limit > final_header_table_size_) { + // The new size must not be greater than the final max header table size + // that the peer acknowledged. + ReportError( + HpackDecodingError::kDynamicTableSizeUpdateIsAboveAcknowledgedSetting, + ""); + return; + } + decoder_tables_.DynamicTableSizeUpdate(size_limit); + if (saw_dynamic_table_size_update_) { + allow_dynamic_table_size_update_ = false; + } else { + saw_dynamic_table_size_update_ = true; + } + // We no longer need to keep an eye out for a lower header table size. + lowest_header_table_size_ = final_header_table_size_; +} + +void HpackDecoderState::OnHpackDecodeError(HpackDecodingError error, + std::string detailed_error) { + HTTP2_DVLOG(2) << "HpackDecoderState::OnHpackDecodeError " + << HpackDecodingErrorToString(error); + if (error_ == HpackDecodingError::kOk) { + ReportError(error, detailed_error); + } +} + +void HpackDecoderState::OnHeaderBlockEnd() { + HTTP2_DVLOG(2) << "HpackDecoderState::OnHeaderBlockEnd"; + if (error_ != HpackDecodingError::kOk) { + return; + } + if (require_dynamic_table_size_update_) { + // Apparently the HPACK block was empty, but we needed it to contain at + // least 1 dynamic table size update. + ReportError(HpackDecodingError::kMissingDynamicTableSizeUpdate, ""); + } else { + listener_->OnHeaderListEnd(); + } +} + +void HpackDecoderState::ReportError(HpackDecodingError error, + std::string detailed_error) { + HTTP2_DVLOG(2) << "HpackDecoderState::ReportError is new=" + << (error_ == HpackDecodingError::kOk ? "true" : "false") + << ", error: " << HpackDecodingErrorToString(error); + if (error_ == HpackDecodingError::kOk) { + listener_->OnHeaderErrorDetected(HpackDecodingErrorToString(error)); + error_ = error; + detailed_error_ = detailed_error; + } +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_state.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_state.h new file mode 100644 index 00000000000..272805f79c6 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_state.h @@ -0,0 +1,138 @@ +// 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. + +// HpackDecoderState maintains the HPACK decompressor state; i.e. updates the +// HPACK dynamic table according to RFC 7541 as the entries in an HPACK block +// are decoded, and reads from the static and dynamic tables in order to build +// complete header entries. Calls an HpackDecoderListener with the completely +// decoded headers (i.e. after resolving table indices into names or values), +// thus translating the decoded HPACK entries into HTTP/2 headers. + +#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STATE_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STATE_H_ + +#include <stddef.h> + +#include <cstdint> + +#include "absl/strings/string_view.h" +#include "quiche/http2/hpack/decoder/hpack_decoder_listener.h" +#include "quiche/http2/hpack/decoder/hpack_decoder_string_buffer.h" +#include "quiche/http2/hpack/decoder/hpack_decoder_tables.h" +#include "quiche/http2/hpack/decoder/hpack_decoding_error.h" +#include "quiche/http2/hpack/decoder/hpack_whole_entry_listener.h" +#include "quiche/http2/hpack/http2_hpack_constants.h" +#include "quiche/common/platform/api/quiche_export.h" + +namespace http2 { +namespace test { +class HpackDecoderStatePeer; +} // namespace test + +class QUICHE_EXPORT_PRIVATE HpackDecoderState : public HpackWholeEntryListener { + public: + explicit HpackDecoderState(HpackDecoderListener* listener); + ~HpackDecoderState() override; + + HpackDecoderState(const HpackDecoderState&) = delete; + HpackDecoderState& operator=(const HpackDecoderState&) = delete; + + // Set the listener to be notified when a whole entry has been decoded, + // including resolving name or name and value references. + // The listener may be changed at any time. + HpackDecoderListener* listener() const { return listener_; } + + // ApplyHeaderTableSizeSetting notifies this object that this endpoint has + // received a SETTINGS ACK frame acknowledging an earlier SETTINGS frame from + // this endpoint specifying a new value for SETTINGS_HEADER_TABLE_SIZE (the + // maximum size of the dynamic table that this endpoint will use to decode + // HPACK blocks). + // Because a SETTINGS frame can contain SETTINGS_HEADER_TABLE_SIZE values, + // the caller must keep track of those multiple changes, and make + // corresponding calls to this method. In particular, a call must be made + // with the lowest value acknowledged by the peer, and a call must be made + // with the final value acknowledged, in that order; additional calls may + // be made if additional values were sent. These calls must be made between + // decoding the SETTINGS ACK, and before the next HPACK block is decoded. + void ApplyHeaderTableSizeSetting(uint32_t max_header_table_size); + + // Returns the most recently applied value of SETTINGS_HEADER_TABLE_SIZE. + size_t GetCurrentHeaderTableSizeSetting() const { + return final_header_table_size_; + } + + // OnHeaderBlockStart notifies this object that we're starting to decode the + // HPACK payload of a HEADERS or PUSH_PROMISE frame. + void OnHeaderBlockStart(); + + // Implement the HpackWholeEntryListener methods, each of which notifies this + // object when an entire entry has been decoded. + void OnIndexedHeader(size_t index) override; + void OnNameIndexAndLiteralValue( + HpackEntryType entry_type, + size_t name_index, + HpackDecoderStringBuffer* value_buffer) override; + void OnLiteralNameAndValue(HpackEntryType entry_type, + HpackDecoderStringBuffer* name_buffer, + HpackDecoderStringBuffer* value_buffer) override; + void OnDynamicTableSizeUpdate(size_t size) override; + void OnHpackDecodeError(HpackDecodingError error, + std::string detailed_error) override; + + // OnHeaderBlockEnd notifies this object that an entire HPACK block has been + // decoded, which might have extended into CONTINUATION blocks. + void OnHeaderBlockEnd(); + + // Returns error code after an error has been detected and reported. + // No further callbacks will be made to the listener. + HpackDecodingError error() const { return error_; } + + size_t GetDynamicTableSize() const { + return decoder_tables_.current_header_table_size(); + } + + const HpackDecoderTables& decoder_tables_for_test() const { + return decoder_tables_; + } + + std::string detailed_error() const { return detailed_error_; } + + private: + friend class test::HpackDecoderStatePeer; + + // Reports an error to the listener IF this is the first error detected. + void ReportError(HpackDecodingError error, std::string detailed_error); + + // The static and dynamic HPACK tables. + HpackDecoderTables decoder_tables_; + + // The listener to be notified of headers, the start and end of header + // lists, and of errors. + HpackDecoderListener* listener_; + + // The most recent HEADER_TABLE_SIZE setting acknowledged by the peer. + uint32_t final_header_table_size_; + + // The lowest HEADER_TABLE_SIZE setting acknowledged by the peer; valid until + // the next HPACK block is decoded. + // TODO(jamessynge): Test raising the HEADER_TABLE_SIZE. + uint32_t lowest_header_table_size_; + + // Must the next (first) HPACK entry be a dynamic table size update? + bool require_dynamic_table_size_update_; + + // May the next (first or second) HPACK entry be a dynamic table size update? + bool allow_dynamic_table_size_update_; + + // Have we already seen a dynamic table size update in this HPACK block? + bool saw_dynamic_table_size_update_; + + // Has an error already been detected and reported to the listener? + HpackDecodingError error_; + std::string detailed_error_; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STATE_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_state_test.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_state_test.cc new file mode 100644 index 00000000000..03a47519ce1 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_state_test.cc @@ -0,0 +1,549 @@ +// 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/hpack/decoder/hpack_decoder_state.h" + +// Tests of HpackDecoderState. + +#include <utility> +#include <vector> + +#include "absl/strings/string_view.h" +#include "quiche/http2/hpack/http2_hpack_constants.h" +#include "quiche/http2/http2_constants.h" +#include "quiche/http2/platform/api/http2_logging.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/common/platform/api/quiche_test_helpers.h" + +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using ::testing::Eq; +using ::testing::Mock; +using ::testing::StrictMock; + +namespace http2 { +namespace test { +class HpackDecoderStatePeer { + public: + static HpackDecoderTables* GetDecoderTables(HpackDecoderState* state) { + return &state->decoder_tables_; + } +}; + +namespace { + +class MockHpackDecoderListener : public HpackDecoderListener { + public: + MOCK_METHOD(void, OnHeaderListStart, (), (override)); + MOCK_METHOD(void, + OnHeader, + (const std::string& name, const std::string& value), + (override)); + MOCK_METHOD(void, OnHeaderListEnd, (), (override)); + MOCK_METHOD(void, + OnHeaderErrorDetected, + (absl::string_view error_message), + (override)); +}; + +enum StringBacking { STATIC, UNBUFFERED, BUFFERED }; + +class HpackDecoderStateTest : public QuicheTest { + protected: + HpackDecoderStateTest() : decoder_state_(&listener_) {} + + HpackDecoderTables* GetDecoderTables() { + return HpackDecoderStatePeer::GetDecoderTables(&decoder_state_); + } + + const HpackStringPair* Lookup(size_t index) { + return GetDecoderTables()->Lookup(index); + } + + size_t current_header_table_size() { + return GetDecoderTables()->current_header_table_size(); + } + + size_t header_table_size_limit() { + return GetDecoderTables()->header_table_size_limit(); + } + + void set_header_table_size_limit(size_t size) { + GetDecoderTables()->DynamicTableSizeUpdate(size); + } + + void SetStringBuffer(const char* s, + StringBacking backing, + HpackDecoderStringBuffer* string_buffer) { + switch (backing) { + case STATIC: + string_buffer->Set(s, true); + break; + case UNBUFFERED: + string_buffer->Set(s, false); + break; + case BUFFERED: + string_buffer->Set(s, false); + string_buffer->BufferStringIfUnbuffered(); + break; + } + } + + void SetName(const char* s, StringBacking backing) { + SetStringBuffer(s, backing, &name_buffer_); + } + + void SetValue(const char* s, StringBacking backing) { + SetStringBuffer(s, backing, &value_buffer_); + } + + void SendStartAndVerifyCallback() { + EXPECT_CALL(listener_, OnHeaderListStart()); + decoder_state_.OnHeaderBlockStart(); + Mock::VerifyAndClearExpectations(&listener_); + } + + void SendSizeUpdate(size_t size) { + decoder_state_.OnDynamicTableSizeUpdate(size); + Mock::VerifyAndClearExpectations(&listener_); + } + + void SendIndexAndVerifyCallback(size_t index, + HpackEntryType /*expected_type*/, + const char* expected_name, + const char* expected_value) { + EXPECT_CALL(listener_, OnHeader(Eq(expected_name), Eq(expected_value))); + decoder_state_.OnIndexedHeader(index); + Mock::VerifyAndClearExpectations(&listener_); + } + + void SendValueAndVerifyCallback(size_t name_index, + HpackEntryType entry_type, + const char* name, + const char* value, + StringBacking value_backing) { + SetValue(value, value_backing); + EXPECT_CALL(listener_, OnHeader(Eq(name), Eq(value))); + decoder_state_.OnNameIndexAndLiteralValue(entry_type, name_index, + &value_buffer_); + Mock::VerifyAndClearExpectations(&listener_); + } + + void SendNameAndValueAndVerifyCallback(HpackEntryType entry_type, + const char* name, + StringBacking name_backing, + const char* value, + StringBacking value_backing) { + SetName(name, name_backing); + SetValue(value, value_backing); + EXPECT_CALL(listener_, OnHeader(Eq(name), Eq(value))); + decoder_state_.OnLiteralNameAndValue(entry_type, &name_buffer_, + &value_buffer_); + Mock::VerifyAndClearExpectations(&listener_); + } + + void SendEndAndVerifyCallback() { + EXPECT_CALL(listener_, OnHeaderListEnd()); + decoder_state_.OnHeaderBlockEnd(); + Mock::VerifyAndClearExpectations(&listener_); + } + + // dynamic_index is one-based, because that is the way RFC 7541 shows it. + AssertionResult VerifyEntry(size_t dynamic_index, + const char* name, + const char* value) { + const HpackStringPair* entry = + Lookup(dynamic_index + kFirstDynamicTableIndex - 1); + VERIFY_NE(entry, nullptr); + VERIFY_EQ(entry->name, name); + VERIFY_EQ(entry->value, value); + return AssertionSuccess(); + } + AssertionResult VerifyNoEntry(size_t dynamic_index) { + const HpackStringPair* entry = + Lookup(dynamic_index + kFirstDynamicTableIndex - 1); + VERIFY_EQ(entry, nullptr); + return AssertionSuccess(); + } + AssertionResult VerifyDynamicTableContents( + const std::vector<std::pair<const char*, const char*>>& entries) { + size_t index = 1; + for (const auto& entry : entries) { + VERIFY_SUCCESS(VerifyEntry(index, entry.first, entry.second)); + ++index; + } + VERIFY_SUCCESS(VerifyNoEntry(index)); + return AssertionSuccess(); + } + + StrictMock<MockHpackDecoderListener> listener_; + HpackDecoderState decoder_state_; + HpackDecoderStringBuffer name_buffer_, value_buffer_; +}; + +// Test based on RFC 7541, section C.3: Request Examples without Huffman Coding. +// This section shows several consecutive header lists, corresponding to HTTP +// requests, on the same connection. +TEST_F(HpackDecoderStateTest, C3_RequestExamples) { + // C.3.1 First Request + // + // Header list to encode: + // + // :method: GET + // :scheme: http + // :path: / + // :authority: www.example.com + + SendStartAndVerifyCallback(); + SendIndexAndVerifyCallback(2, HpackEntryType::kIndexedHeader, ":method", + "GET"); + SendIndexAndVerifyCallback(6, HpackEntryType::kIndexedHeader, ":scheme", + "http"); + SendIndexAndVerifyCallback(4, HpackEntryType::kIndexedHeader, ":path", "/"); + SendValueAndVerifyCallback(1, HpackEntryType::kIndexedLiteralHeader, + ":authority", "www.example.com", UNBUFFERED); + SendEndAndVerifyCallback(); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 57) :authority: www.example.com + // Table size: 57 + + ASSERT_TRUE(VerifyDynamicTableContents({{":authority", "www.example.com"}})); + ASSERT_EQ(57u, current_header_table_size()); + + // C.3.2 Second Request + // + // Header list to encode: + // + // :method: GET + // :scheme: http + // :path: / + // :authority: www.example.com + // cache-control: no-cache + + SendStartAndVerifyCallback(); + SendIndexAndVerifyCallback(2, HpackEntryType::kIndexedHeader, ":method", + "GET"); + SendIndexAndVerifyCallback(6, HpackEntryType::kIndexedHeader, ":scheme", + "http"); + SendIndexAndVerifyCallback(4, HpackEntryType::kIndexedHeader, ":path", "/"); + SendIndexAndVerifyCallback(62, HpackEntryType::kIndexedHeader, ":authority", + "www.example.com"); + SendValueAndVerifyCallback(24, HpackEntryType::kIndexedLiteralHeader, + "cache-control", "no-cache", UNBUFFERED); + SendEndAndVerifyCallback(); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 53) cache-control: no-cache + // [ 2] (s = 57) :authority: www.example.com + // Table size: 110 + + ASSERT_TRUE(VerifyDynamicTableContents( + {{"cache-control", "no-cache"}, {":authority", "www.example.com"}})); + ASSERT_EQ(110u, current_header_table_size()); + + // C.3.3 Third Request + // + // Header list to encode: + // + // :method: GET + // :scheme: https + // :path: /index.html + // :authority: www.example.com + // custom-key: custom-value + + SendStartAndVerifyCallback(); + SendIndexAndVerifyCallback(2, HpackEntryType::kIndexedHeader, ":method", + "GET"); + SendIndexAndVerifyCallback(7, HpackEntryType::kIndexedHeader, ":scheme", + "https"); + SendIndexAndVerifyCallback(5, HpackEntryType::kIndexedHeader, ":path", + "/index.html"); + SendIndexAndVerifyCallback(63, HpackEntryType::kIndexedHeader, ":authority", + "www.example.com"); + SendNameAndValueAndVerifyCallback(HpackEntryType::kIndexedLiteralHeader, + "custom-key", UNBUFFERED, "custom-value", + UNBUFFERED); + SendEndAndVerifyCallback(); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 54) custom-key: custom-value + // [ 2] (s = 53) cache-control: no-cache + // [ 3] (s = 57) :authority: www.example.com + // Table size: 164 + + ASSERT_TRUE(VerifyDynamicTableContents({{"custom-key", "custom-value"}, + {"cache-control", "no-cache"}, + {":authority", "www.example.com"}})); + ASSERT_EQ(164u, current_header_table_size()); +} + +// Test based on RFC 7541, section C.5: Response Examples without Huffman +// Coding. This section shows several consecutive header lists, corresponding +// to HTTP responses, on the same connection. The HTTP/2 setting parameter +// SETTINGS_HEADER_TABLE_SIZE is set to the value of 256 octets, causing +// some evictions to occur. +TEST_F(HpackDecoderStateTest, C5_ResponseExamples) { + set_header_table_size_limit(256); + + // C.5.1 First Response + // + // Header list to encode: + // + // :status: 302 + // cache-control: private + // date: Mon, 21 Oct 2013 20:13:21 GMT + // location: https://www.example.com + + SendStartAndVerifyCallback(); + SendValueAndVerifyCallback(8, HpackEntryType::kIndexedLiteralHeader, + ":status", "302", BUFFERED); + SendValueAndVerifyCallback(24, HpackEntryType::kIndexedLiteralHeader, + "cache-control", "private", UNBUFFERED); + SendValueAndVerifyCallback(33, HpackEntryType::kIndexedLiteralHeader, "date", + "Mon, 21 Oct 2013 20:13:21 GMT", UNBUFFERED); + SendValueAndVerifyCallback(46, HpackEntryType::kIndexedLiteralHeader, + "location", "https://www.example.com", UNBUFFERED); + SendEndAndVerifyCallback(); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 63) location: https://www.example.com + // [ 2] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT + // [ 3] (s = 52) cache-control: private + // [ 4] (s = 42) :status: 302 + // Table size: 222 + + ASSERT_TRUE( + VerifyDynamicTableContents({{"location", "https://www.example.com"}, + {"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + {"cache-control", "private"}, + {":status", "302"}})); + ASSERT_EQ(222u, current_header_table_size()); + + // C.5.2 Second Response + // + // The (":status", "302") header field is evicted from the dynamic table to + // free space to allow adding the (":status", "307") header field. + // + // Header list to encode: + // + // :status: 307 + // cache-control: private + // date: Mon, 21 Oct 2013 20:13:21 GMT + // location: https://www.example.com + + SendStartAndVerifyCallback(); + SendValueAndVerifyCallback(8, HpackEntryType::kIndexedLiteralHeader, + ":status", "307", BUFFERED); + SendIndexAndVerifyCallback(65, HpackEntryType::kIndexedHeader, + "cache-control", "private"); + SendIndexAndVerifyCallback(64, HpackEntryType::kIndexedHeader, "date", + "Mon, 21 Oct 2013 20:13:21 GMT"); + SendIndexAndVerifyCallback(63, HpackEntryType::kIndexedHeader, "location", + "https://www.example.com"); + SendEndAndVerifyCallback(); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 42) :status: 307 + // [ 2] (s = 63) location: https://www.example.com + // [ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT + // [ 4] (s = 52) cache-control: private + // Table size: 222 + + ASSERT_TRUE( + VerifyDynamicTableContents({{":status", "307"}, + {"location", "https://www.example.com"}, + {"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + {"cache-control", "private"}})); + ASSERT_EQ(222u, current_header_table_size()); + + // C.5.3 Third Response + // + // Several header fields are evicted from the dynamic table during the + // processing of this header list. + // + // Header list to encode: + // + // :status: 200 + // cache-control: private + // date: Mon, 21 Oct 2013 20:13:22 GMT + // location: https://www.example.com + // content-encoding: gzip + // set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1 + + SendStartAndVerifyCallback(); + SendIndexAndVerifyCallback(8, HpackEntryType::kIndexedHeader, ":status", + "200"); + SendIndexAndVerifyCallback(65, HpackEntryType::kIndexedHeader, + "cache-control", "private"); + SendValueAndVerifyCallback(33, HpackEntryType::kIndexedLiteralHeader, "date", + "Mon, 21 Oct 2013 20:13:22 GMT", BUFFERED); + SendIndexAndVerifyCallback(64, HpackEntryType::kIndexedHeader, "location", + "https://www.example.com"); + SendValueAndVerifyCallback(26, HpackEntryType::kIndexedLiteralHeader, + "content-encoding", "gzip", UNBUFFERED); + SendValueAndVerifyCallback( + 55, HpackEntryType::kIndexedLiteralHeader, "set-cookie", + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", BUFFERED); + SendEndAndVerifyCallback(); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; + // max-age=3600; version=1 + // [ 2] (s = 52) content-encoding: gzip + // [ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:22 GMT + // Table size: 215 + + ASSERT_TRUE(VerifyDynamicTableContents( + {{"set-cookie", + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"}, + {"content-encoding", "gzip"}, + {"date", "Mon, 21 Oct 2013 20:13:22 GMT"}})); + ASSERT_EQ(215u, current_header_table_size()); +} + +// Confirm that the table size can be changed, but at most twice. +TEST_F(HpackDecoderStateTest, OptionalTableSizeChanges) { + SendStartAndVerifyCallback(); + EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(), + header_table_size_limit()); + SendSizeUpdate(1024); + EXPECT_EQ(1024u, header_table_size_limit()); + SendSizeUpdate(0); + EXPECT_EQ(0u, header_table_size_limit()); + + // Three updates aren't allowed. + EXPECT_CALL(listener_, OnHeaderErrorDetected( + Eq("Dynamic table size update not allowed"))); + SendSizeUpdate(0); +} + +// Confirm that required size updates are indeed required before headers. +TEST_F(HpackDecoderStateTest, RequiredTableSizeChangeBeforeHeader) { + EXPECT_EQ(4096u, decoder_state_.GetCurrentHeaderTableSizeSetting()); + decoder_state_.ApplyHeaderTableSizeSetting(1024); + decoder_state_.ApplyHeaderTableSizeSetting(2048); + EXPECT_EQ(2048u, decoder_state_.GetCurrentHeaderTableSizeSetting()); + + // First provide the required update, and an allowed second update. + SendStartAndVerifyCallback(); + EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(), + header_table_size_limit()); + SendSizeUpdate(1024); + EXPECT_EQ(1024u, header_table_size_limit()); + SendSizeUpdate(1500); + EXPECT_EQ(1500u, header_table_size_limit()); + SendEndAndVerifyCallback(); + + // Another HPACK block, but this time missing the required size update. + decoder_state_.ApplyHeaderTableSizeSetting(1024); + EXPECT_EQ(1024u, decoder_state_.GetCurrentHeaderTableSizeSetting()); + SendStartAndVerifyCallback(); + EXPECT_CALL(listener_, + OnHeaderErrorDetected(Eq("Missing dynamic table size update"))); + decoder_state_.OnIndexedHeader(1); + + // Further decoded entries are ignored. + decoder_state_.OnIndexedHeader(1); + decoder_state_.OnDynamicTableSizeUpdate(1); + SetValue("value", UNBUFFERED); + decoder_state_.OnNameIndexAndLiteralValue( + HpackEntryType::kIndexedLiteralHeader, 4, &value_buffer_); + SetName("name", UNBUFFERED); + decoder_state_.OnLiteralNameAndValue(HpackEntryType::kIndexedLiteralHeader, + &name_buffer_, &value_buffer_); + decoder_state_.OnHeaderBlockEnd(); + decoder_state_.OnHpackDecodeError(HpackDecodingError::kIndexVarintError, ""); +} + +// Confirm that required size updates are validated. +TEST_F(HpackDecoderStateTest, InvalidRequiredSizeUpdate) { + // Require a size update, but provide one that isn't small enough. + decoder_state_.ApplyHeaderTableSizeSetting(1024); + SendStartAndVerifyCallback(); + EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(), + header_table_size_limit()); + EXPECT_CALL( + listener_, + OnHeaderErrorDetected( + Eq("Initial dynamic table size update is above low water mark"))); + SendSizeUpdate(2048); +} + +// Confirm that required size updates are indeed required before the end. +TEST_F(HpackDecoderStateTest, RequiredTableSizeChangeBeforeEnd) { + decoder_state_.ApplyHeaderTableSizeSetting(1024); + SendStartAndVerifyCallback(); + EXPECT_CALL(listener_, + OnHeaderErrorDetected(Eq("Missing dynamic table size update"))); + decoder_state_.OnHeaderBlockEnd(); +} + +// Confirm that optional size updates are validated. +TEST_F(HpackDecoderStateTest, InvalidOptionalSizeUpdate) { + // Require a size update, but provide one that isn't small enough. + SendStartAndVerifyCallback(); + EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(), + header_table_size_limit()); + EXPECT_CALL(listener_, + OnHeaderErrorDetected(Eq( + "Dynamic table size update is above acknowledged setting"))); + SendSizeUpdate(Http2SettingsInfo::DefaultHeaderTableSize() + 1); +} + +TEST_F(HpackDecoderStateTest, InvalidStaticIndex) { + SendStartAndVerifyCallback(); + EXPECT_CALL(listener_, + OnHeaderErrorDetected( + Eq("Invalid index in indexed header field representation"))); + decoder_state_.OnIndexedHeader(0); +} + +TEST_F(HpackDecoderStateTest, InvalidDynamicIndex) { + SendStartAndVerifyCallback(); + EXPECT_CALL(listener_, + OnHeaderErrorDetected( + Eq("Invalid index in indexed header field representation"))); + decoder_state_.OnIndexedHeader(kFirstDynamicTableIndex); +} + +TEST_F(HpackDecoderStateTest, InvalidNameIndex) { + SendStartAndVerifyCallback(); + EXPECT_CALL(listener_, + OnHeaderErrorDetected(Eq("Invalid index in literal header field " + "with indexed name representation"))); + SetValue("value", UNBUFFERED); + decoder_state_.OnNameIndexAndLiteralValue( + HpackEntryType::kIndexedLiteralHeader, kFirstDynamicTableIndex, + &value_buffer_); +} + +TEST_F(HpackDecoderStateTest, ErrorsSuppressCallbacks) { + SendStartAndVerifyCallback(); + EXPECT_CALL(listener_, + OnHeaderErrorDetected(Eq("Name Huffman encoding error"))); + decoder_state_.OnHpackDecodeError(HpackDecodingError::kNameHuffmanError, ""); + + // Further decoded entries are ignored. + decoder_state_.OnIndexedHeader(1); + decoder_state_.OnDynamicTableSizeUpdate(1); + SetValue("value", UNBUFFERED); + decoder_state_.OnNameIndexAndLiteralValue( + HpackEntryType::kIndexedLiteralHeader, 4, &value_buffer_); + SetName("name", UNBUFFERED); + decoder_state_.OnLiteralNameAndValue(HpackEntryType::kIndexedLiteralHeader, + &name_buffer_, &value_buffer_); + decoder_state_.OnHeaderBlockEnd(); + decoder_state_.OnHpackDecodeError(HpackDecodingError::kIndexVarintError, ""); +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_string_buffer.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_string_buffer.cc new file mode 100644 index 00000000000..e928686ceb9 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_string_buffer.cc @@ -0,0 +1,239 @@ +// 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/hpack/decoder/hpack_decoder_string_buffer.h" + +#include <utility> + +#include "quiche/http2/platform/api/http2_bug_tracker.h" +#include "quiche/http2/platform/api/http2_logging.h" + +namespace http2 { + +std::ostream& operator<<(std::ostream& out, + const HpackDecoderStringBuffer::State v) { + switch (v) { + case HpackDecoderStringBuffer::State::RESET: + return out << "RESET"; + case HpackDecoderStringBuffer::State::COLLECTING: + return out << "COLLECTING"; + case HpackDecoderStringBuffer::State::COMPLETE: + return out << "COMPLETE"; + } + // Since the value doesn't come over the wire, only a programming bug should + // result in reaching this point. + int unknown = static_cast<int>(v); + HTTP2_BUG(http2_bug_50_1) + << "Invalid HpackDecoderStringBuffer::State: " << unknown; + return out << "HpackDecoderStringBuffer::State(" << unknown << ")"; +} + +std::ostream& operator<<(std::ostream& out, + const HpackDecoderStringBuffer::Backing v) { + switch (v) { + case HpackDecoderStringBuffer::Backing::RESET: + return out << "RESET"; + case HpackDecoderStringBuffer::Backing::UNBUFFERED: + return out << "UNBUFFERED"; + case HpackDecoderStringBuffer::Backing::BUFFERED: + return out << "BUFFERED"; + case HpackDecoderStringBuffer::Backing::STATIC: + return out << "STATIC"; + } + // Since the value doesn't come over the wire, only a programming bug should + // result in reaching this point. + auto v2 = static_cast<int>(v); + HTTP2_BUG(http2_bug_50_2) + << "Invalid HpackDecoderStringBuffer::Backing: " << v2; + return out << "HpackDecoderStringBuffer::Backing(" << v2 << ")"; +} + +HpackDecoderStringBuffer::HpackDecoderStringBuffer() + : remaining_len_(0), + is_huffman_encoded_(false), + state_(State::RESET), + backing_(Backing::RESET) {} +HpackDecoderStringBuffer::~HpackDecoderStringBuffer() = default; + +void HpackDecoderStringBuffer::Reset() { + HTTP2_DVLOG(3) << "HpackDecoderStringBuffer::Reset"; + state_ = State::RESET; +} + +void HpackDecoderStringBuffer::Set(absl::string_view value, bool is_static) { + HTTP2_DVLOG(2) << "HpackDecoderStringBuffer::Set"; + QUICHE_DCHECK_EQ(state_, State::RESET); + value_ = value; + state_ = State::COMPLETE; + backing_ = is_static ? Backing::STATIC : Backing::UNBUFFERED; + // TODO(jamessynge): Determine which of these two fields must be set. + remaining_len_ = 0; + is_huffman_encoded_ = false; +} + +void HpackDecoderStringBuffer::OnStart(bool huffman_encoded, size_t len) { + HTTP2_DVLOG(2) << "HpackDecoderStringBuffer::OnStart"; + QUICHE_DCHECK_EQ(state_, State::RESET); + + remaining_len_ = len; + is_huffman_encoded_ = huffman_encoded; + state_ = State::COLLECTING; + + if (huffman_encoded) { + // We don't set, clear or use value_ for buffered strings until OnEnd. + decoder_.Reset(); + buffer_.clear(); + backing_ = Backing::BUFFERED; + + // Reserve space in buffer_ for the uncompressed string, assuming the + // maximum expansion. The shortest Huffman codes in the RFC are 5 bits long, + // which then expand to 8 bits during decoding (i.e. each code is for one + // plain text octet, aka byte), so the maximum size is 60% longer than the + // encoded size. + len = len * 8 / 5; + if (buffer_.capacity() < len) { + buffer_.reserve(len); + } + } else { + // Assume for now that we won't need to use buffer_, so don't reserve space + // in it. + backing_ = Backing::RESET; + // OnData is not called for empty (zero length) strings, so make sure that + // value_ is cleared. + value_ = absl::string_view(); + } +} + +bool HpackDecoderStringBuffer::OnData(const char* data, size_t len) { + HTTP2_DVLOG(2) << "HpackDecoderStringBuffer::OnData state=" << state_ + << ", backing=" << backing_; + QUICHE_DCHECK_EQ(state_, State::COLLECTING); + QUICHE_DCHECK_LE(len, remaining_len_); + remaining_len_ -= len; + + if (is_huffman_encoded_) { + QUICHE_DCHECK_EQ(backing_, Backing::BUFFERED); + return decoder_.Decode(absl::string_view(data, len), &buffer_); + } + + if (backing_ == Backing::RESET) { + // This is the first call to OnData. If data contains the entire string, + // don't copy the string. If we later find that the HPACK entry is split + // across input buffers, then we'll copy the string into buffer_. + if (remaining_len_ == 0) { + value_ = absl::string_view(data, len); + backing_ = Backing::UNBUFFERED; + return true; + } + + // We need to buffer the string because it is split across input buffers. + // Reserve space in buffer_ for the entire string. + backing_ = Backing::BUFFERED; + buffer_.reserve(remaining_len_ + len); + buffer_.assign(data, len); + return true; + } + + // This is not the first call to OnData for this string, so it should be + // buffered. + QUICHE_DCHECK_EQ(backing_, Backing::BUFFERED); + + // Append to the current contents of the buffer. + buffer_.append(data, len); + return true; +} + +bool HpackDecoderStringBuffer::OnEnd() { + HTTP2_DVLOG(2) << "HpackDecoderStringBuffer::OnEnd"; + QUICHE_DCHECK_EQ(state_, State::COLLECTING); + QUICHE_DCHECK_EQ(0u, remaining_len_); + + if (is_huffman_encoded_) { + QUICHE_DCHECK_EQ(backing_, Backing::BUFFERED); + // Did the Huffman encoding of the string end properly? + if (!decoder_.InputProperlyTerminated()) { + return false; // No, it didn't. + } + value_ = buffer_; + } else if (backing_ == Backing::BUFFERED) { + value_ = buffer_; + } + state_ = State::COMPLETE; + return true; +} + +void HpackDecoderStringBuffer::BufferStringIfUnbuffered() { + HTTP2_DVLOG(3) << "HpackDecoderStringBuffer::BufferStringIfUnbuffered state=" + << state_ << ", backing=" << backing_; + if (state_ != State::RESET && backing_ == Backing::UNBUFFERED) { + HTTP2_DVLOG(2) + << "HpackDecoderStringBuffer buffering std::string of length " + << value_.size(); + buffer_.assign(value_.data(), value_.size()); + if (state_ == State::COMPLETE) { + value_ = buffer_; + } + backing_ = Backing::BUFFERED; + } +} + +bool HpackDecoderStringBuffer::IsBuffered() const { + HTTP2_DVLOG(3) << "HpackDecoderStringBuffer::IsBuffered"; + return state_ != State::RESET && backing_ == Backing::BUFFERED; +} + +size_t HpackDecoderStringBuffer::BufferedLength() const { + HTTP2_DVLOG(3) << "HpackDecoderStringBuffer::BufferedLength"; + return IsBuffered() ? buffer_.size() : 0; +} + +absl::string_view HpackDecoderStringBuffer::str() const { + HTTP2_DVLOG(3) << "HpackDecoderStringBuffer::str"; + QUICHE_DCHECK_EQ(state_, State::COMPLETE); + return value_; +} + +absl::string_view HpackDecoderStringBuffer::GetStringIfComplete() const { + if (state_ != State::COMPLETE) { + return {}; + } + return str(); +} + +std::string HpackDecoderStringBuffer::ReleaseString() { + HTTP2_DVLOG(3) << "HpackDecoderStringBuffer::ReleaseString"; + QUICHE_DCHECK_EQ(state_, State::COMPLETE); + QUICHE_DCHECK_EQ(backing_, Backing::BUFFERED); + if (state_ == State::COMPLETE) { + state_ = State::RESET; + if (backing_ == Backing::BUFFERED) { + return std::move(buffer_); + } else { + return std::string(value_); + } + } + return ""; +} + +void HpackDecoderStringBuffer::OutputDebugStringTo(std::ostream& out) const { + out << "{state=" << state_; + if (state_ != State::RESET) { + out << ", backing=" << backing_; + out << ", remaining_len=" << remaining_len_; + out << ", is_huffman_encoded=" << is_huffman_encoded_; + if (backing_ == Backing::BUFFERED) { + out << ", buffer: " << buffer_; + } else { + out << ", value: " << value_; + } + } + out << "}"; +} + +std::ostream& operator<<(std::ostream& out, const HpackDecoderStringBuffer& v) { + v.OutputDebugStringTo(out); + return out; +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_string_buffer.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_string_buffer.h new file mode 100644 index 00000000000..a1877c88496 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_string_buffer.h @@ -0,0 +1,102 @@ +// 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_HPACK_DECODER_HPACK_DECODER_STRING_BUFFER_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STRING_BUFFER_H_ + +// HpackDecoderStringBuffer helps an HPACK decoder to avoid copies of a string +// literal (name or value) except when necessary (e.g. when split across two +// or more HPACK block fragments). + +#include <stddef.h> + +#include <ostream> +#include <string> + +#include "absl/strings/string_view.h" +#include "quiche/http2/hpack/huffman/hpack_huffman_decoder.h" +#include "quiche/common/platform/api/quiche_export.h" + +namespace http2 { + +class QUICHE_EXPORT_PRIVATE HpackDecoderStringBuffer { + public: + enum class State : uint8_t { RESET, COLLECTING, COMPLETE }; + enum class Backing : uint8_t { RESET, UNBUFFERED, BUFFERED, STATIC }; + + HpackDecoderStringBuffer(); + ~HpackDecoderStringBuffer(); + + HpackDecoderStringBuffer(const HpackDecoderStringBuffer&) = delete; + HpackDecoderStringBuffer& operator=(const HpackDecoderStringBuffer&) = delete; + + void Reset(); + void Set(absl::string_view value, bool is_static); + + // Note that for Huffman encoded strings the length of the string after + // decoding may be larger (expected), the same or even smaller; the latter + // are unlikely, but possible if the encoder makes odd choices. + void OnStart(bool huffman_encoded, size_t len); + bool OnData(const char* data, size_t len); + bool OnEnd(); + void BufferStringIfUnbuffered(); + bool IsBuffered() const; + size_t BufferedLength() const; + + // Accessors for the completely collected string (i.e. Set or OnEnd has just + // been called, and no reset of the state has occurred). + + // Returns a string_view pointing to the backing store for the string, + // either the internal buffer or the original transport buffer (e.g. for a + // literal value that wasn't Huffman encoded, and that wasn't split across + // transport buffers). + absl::string_view str() const; + + // Same as str() if state_ is COMPLETE. Otherwise, returns empty string piece. + absl::string_view GetStringIfComplete() const; + + // Returns the completely collected string by value, using std::move in an + // effort to avoid unnecessary copies. ReleaseString() must not be called + // unless the string has been buffered (to avoid forcing a potentially + // unnecessary copy). ReleaseString() also resets the instance so that it can + // be used to collect another string. + std::string ReleaseString(); + + State state_for_testing() const { return state_; } + Backing backing_for_testing() const { return backing_; } + void OutputDebugStringTo(std::ostream& out) const; + + private: + // Storage for the string being buffered, if buffering is necessary + // (e.g. if Huffman encoded, buffer_ is storage for the decoded string). + std::string buffer_; + + // The string_view to be returned by HpackDecoderStringBuffer::str(). If + // a string has been collected, but not buffered, value_ points to that + // string. + absl::string_view value_; + + // The decoder to use if the string is Huffman encoded. + HpackHuffmanDecoder decoder_; + + // Count of bytes not yet passed to OnData. + size_t remaining_len_; + + // Is the HPACK string Huffman encoded? + bool is_huffman_encoded_; + + // State of the string decoding process. + State state_; + + // Where is the string stored? + Backing backing_; +}; + +QUICHE_EXPORT_PRIVATE std::ostream& operator<<( + std::ostream& out, + const HpackDecoderStringBuffer& v); + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STRING_BUFFER_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc new file mode 100644 index 00000000000..63785388ffc --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc @@ -0,0 +1,249 @@ +// 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/hpack/decoder/hpack_decoder_string_buffer.h" + +// Tests of HpackDecoderStringBuffer. + +#include <initializer_list> + +#include "absl/strings/escaping.h" +#include "quiche/http2/platform/api/http2_logging.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/common/platform/api/quiche_test_helpers.h" + +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using ::testing::HasSubstr; + +namespace http2 { +namespace test { +namespace { + +class HpackDecoderStringBufferTest : public QuicheTest { + protected: + typedef HpackDecoderStringBuffer::State State; + typedef HpackDecoderStringBuffer::Backing Backing; + + State state() const { return buf_.state_for_testing(); } + Backing backing() const { return buf_.backing_for_testing(); } + + // We want to know that HTTP2_LOG(x) << buf_ will work in production should + // that be needed, so we test that it outputs the expected values. + AssertionResult VerifyLogHasSubstrs(std::initializer_list<std::string> strs) { + HTTP2_VLOG(1) << buf_; + std::ostringstream ss; + buf_.OutputDebugStringTo(ss); + std::string dbg_str(ss.str()); + for (const auto& expected : strs) { + VERIFY_THAT(dbg_str, HasSubstr(expected)); + } + return AssertionSuccess(); + } + + HpackDecoderStringBuffer buf_; +}; + +TEST_F(HpackDecoderStringBufferTest, SetStatic) { + absl::string_view data("static string"); + + EXPECT_EQ(state(), State::RESET); + EXPECT_TRUE(VerifyLogHasSubstrs({"state=RESET"})); + + buf_.Set(data, /*is_static*/ true); + HTTP2_LOG(INFO) << buf_; + EXPECT_EQ(state(), State::COMPLETE); + EXPECT_EQ(backing(), Backing::STATIC); + EXPECT_EQ(data, buf_.str()); + EXPECT_EQ(data.data(), buf_.str().data()); + EXPECT_TRUE(VerifyLogHasSubstrs( + {"state=COMPLETE", "backing=STATIC", "value: static string"})); + + // The string is static, so BufferStringIfUnbuffered won't change anything. + buf_.BufferStringIfUnbuffered(); + EXPECT_EQ(state(), State::COMPLETE); + EXPECT_EQ(backing(), Backing::STATIC); + EXPECT_EQ(data, buf_.str()); + EXPECT_EQ(data.data(), buf_.str().data()); + EXPECT_TRUE(VerifyLogHasSubstrs( + {"state=COMPLETE", "backing=STATIC", "value: static string"})); +} + +TEST_F(HpackDecoderStringBufferTest, PlainWhole) { + absl::string_view data("some text."); + + HTTP2_LOG(INFO) << buf_; + EXPECT_EQ(state(), State::RESET); + + buf_.OnStart(/*huffman_encoded*/ false, data.size()); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::RESET); + HTTP2_LOG(INFO) << buf_; + + EXPECT_TRUE(buf_.OnData(data.data(), data.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::UNBUFFERED); + + EXPECT_TRUE(buf_.OnEnd()); + EXPECT_EQ(state(), State::COMPLETE); + EXPECT_EQ(backing(), Backing::UNBUFFERED); + EXPECT_EQ(0u, buf_.BufferedLength()); + EXPECT_TRUE(VerifyLogHasSubstrs( + {"state=COMPLETE", "backing=UNBUFFERED", "value: some text."})); + + // We expect that the string buffer points to the passed in + // string_view's backing store. + EXPECT_EQ(data.data(), buf_.str().data()); + + // Now force it to buffer the string, after which it will still have the same + // string value, but the backing store will be different. + buf_.BufferStringIfUnbuffered(); + HTTP2_LOG(INFO) << buf_; + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), data.size()); + EXPECT_EQ(data, buf_.str()); + EXPECT_NE(data.data(), buf_.str().data()); + EXPECT_TRUE(VerifyLogHasSubstrs( + {"state=COMPLETE", "backing=BUFFERED", "buffer: some text."})); +} + +TEST_F(HpackDecoderStringBufferTest, PlainSplit) { + absl::string_view data("some text."); + absl::string_view part1 = data.substr(0, 1); + absl::string_view part2 = data.substr(1); + + EXPECT_EQ(state(), State::RESET); + buf_.OnStart(/*huffman_encoded*/ false, data.size()); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::RESET); + + // OnData with only a part of the data, not the whole, so buf_ will buffer + // the data. + EXPECT_TRUE(buf_.OnData(part1.data(), part1.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), part1.size()); + HTTP2_LOG(INFO) << buf_; + + EXPECT_TRUE(buf_.OnData(part2.data(), part2.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), data.size()); + + EXPECT_TRUE(buf_.OnEnd()); + EXPECT_EQ(state(), State::COMPLETE); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), data.size()); + HTTP2_LOG(INFO) << buf_; + + absl::string_view buffered = buf_.str(); + EXPECT_EQ(data, buffered); + EXPECT_NE(data.data(), buffered.data()); + + // The string is already buffered, so BufferStringIfUnbuffered should not make + // any change. + buf_.BufferStringIfUnbuffered(); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), data.size()); + EXPECT_EQ(buffered, buf_.str()); + EXPECT_EQ(buffered.data(), buf_.str().data()); +} + +TEST_F(HpackDecoderStringBufferTest, HuffmanWhole) { + std::string encoded = absl::HexStringToBytes("f1e3c2e5f23a6ba0ab90f4ff"); + absl::string_view decoded("www.example.com"); + + EXPECT_EQ(state(), State::RESET); + buf_.OnStart(/*huffman_encoded*/ true, encoded.size()); + EXPECT_EQ(state(), State::COLLECTING); + + EXPECT_TRUE(buf_.OnData(encoded.data(), encoded.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + + EXPECT_TRUE(buf_.OnEnd()); + EXPECT_EQ(state(), State::COMPLETE); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), decoded.size()); + EXPECT_EQ(decoded, buf_.str()); + EXPECT_TRUE(VerifyLogHasSubstrs( + {"{state=COMPLETE", "backing=BUFFERED", "buffer: www.example.com}"})); + + std::string s = buf_.ReleaseString(); + EXPECT_EQ(s, decoded); + EXPECT_EQ(state(), State::RESET); +} + +TEST_F(HpackDecoderStringBufferTest, HuffmanSplit) { + std::string encoded = absl::HexStringToBytes("f1e3c2e5f23a6ba0ab90f4ff"); + std::string part1 = encoded.substr(0, 5); + std::string part2 = encoded.substr(5); + absl::string_view decoded("www.example.com"); + + EXPECT_EQ(state(), State::RESET); + buf_.OnStart(/*huffman_encoded*/ true, encoded.size()); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(0u, buf_.BufferedLength()); + HTTP2_LOG(INFO) << buf_; + + EXPECT_TRUE(buf_.OnData(part1.data(), part1.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_GT(buf_.BufferedLength(), 0u); + EXPECT_LT(buf_.BufferedLength(), decoded.size()); + HTTP2_LOG(INFO) << buf_; + + EXPECT_TRUE(buf_.OnData(part2.data(), part2.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), decoded.size()); + HTTP2_LOG(INFO) << buf_; + + EXPECT_TRUE(buf_.OnEnd()); + EXPECT_EQ(state(), State::COMPLETE); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), decoded.size()); + EXPECT_EQ(decoded, buf_.str()); + HTTP2_LOG(INFO) << buf_; + + buf_.Reset(); + EXPECT_EQ(state(), State::RESET); + HTTP2_LOG(INFO) << buf_; +} + +TEST_F(HpackDecoderStringBufferTest, InvalidHuffmanOnData) { + // Explicitly encode the End-of-String symbol, a no-no. + std::string encoded = absl::HexStringToBytes("ffffffff"); + + buf_.OnStart(/*huffman_encoded*/ true, encoded.size()); + EXPECT_EQ(state(), State::COLLECTING); + + EXPECT_FALSE(buf_.OnData(encoded.data(), encoded.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + + HTTP2_LOG(INFO) << buf_; +} + +TEST_F(HpackDecoderStringBufferTest, InvalidHuffmanOnEnd) { + // Last byte of string doesn't end with prefix of End-of-String symbol. + std::string encoded = absl::HexStringToBytes("00"); + + buf_.OnStart(/*huffman_encoded*/ true, encoded.size()); + EXPECT_EQ(state(), State::COLLECTING); + + EXPECT_TRUE(buf_.OnData(encoded.data(), encoded.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + + EXPECT_FALSE(buf_.OnEnd()); + HTTP2_LOG(INFO) << buf_; +} + +// TODO(jamessynge): Add tests for ReleaseString(). + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_tables.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_tables.cc new file mode 100644 index 00000000000..233e4b68358 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_tables.cc @@ -0,0 +1,148 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/http2/hpack/decoder/hpack_decoder_tables.h" + +#include "absl/strings/str_cat.h" +#include "quiche/http2/hpack/http2_hpack_constants.h" +#include "quiche/http2/platform/api/http2_logging.h" + +namespace http2 { +namespace { + +std::vector<HpackStringPair>* MakeStaticTable() { + auto* ptr = new std::vector<HpackStringPair>(); + ptr->reserve(kFirstDynamicTableIndex); + ptr->emplace_back("", ""); + +#define STATIC_TABLE_ENTRY(name, value, index) \ + QUICHE_DCHECK_EQ(ptr->size(), static_cast<size_t>(index)); \ + ptr->emplace_back(name, value) + +#include "quiche/http2/hpack/hpack_static_table_entries.inc" + +#undef STATIC_TABLE_ENTRY + + return ptr; +} + +const std::vector<HpackStringPair>* GetStaticTable() { + static const std::vector<HpackStringPair>* const g_static_table = + MakeStaticTable(); + return g_static_table; +} + +} // namespace + +HpackStringPair::HpackStringPair(std::string name, std::string value) + : name(std::move(name)), value(std::move(value)) { + HTTP2_DVLOG(3) << DebugString() << " ctor"; +} + +HpackStringPair::~HpackStringPair() { + HTTP2_DVLOG(3) << DebugString() << " dtor"; +} + +std::string HpackStringPair::DebugString() const { + return absl::StrCat("HpackStringPair(name=", name, ", value=", value, ")"); +} + +std::ostream& operator<<(std::ostream& os, const HpackStringPair& p) { + os << p.DebugString(); + return os; +} + +HpackDecoderStaticTable::HpackDecoderStaticTable( + const std::vector<HpackStringPair>* table) + : table_(table) {} + +HpackDecoderStaticTable::HpackDecoderStaticTable() : table_(GetStaticTable()) {} + +const HpackStringPair* HpackDecoderStaticTable::Lookup(size_t index) const { + if (0 < index && index < kFirstDynamicTableIndex) { + return &((*table_)[index]); + } + return nullptr; +} + +HpackDecoderDynamicTable::HpackDecoderDynamicTable() + : insert_count_(kFirstDynamicTableIndex - 1) {} +HpackDecoderDynamicTable::~HpackDecoderDynamicTable() = default; + +void HpackDecoderDynamicTable::DynamicTableSizeUpdate(size_t size_limit) { + HTTP2_DVLOG(3) << "HpackDecoderDynamicTable::DynamicTableSizeUpdate " + << size_limit; + EnsureSizeNoMoreThan(size_limit); + QUICHE_DCHECK_LE(current_size_, size_limit); + size_limit_ = size_limit; +} + +// TODO(jamessynge): Check somewhere before here that names received from the +// peer are valid (e.g. are lower-case, no whitespace, etc.). +void HpackDecoderDynamicTable::Insert(std::string name, std::string value) { + HpackStringPair entry(std::move(name), std::move(value)); + size_t entry_size = entry.size(); + HTTP2_DVLOG(2) << "InsertEntry of size=" << entry_size + << "\n name: " << entry.name + << "\n value: " << entry.value; + if (entry_size > size_limit_) { + HTTP2_DVLOG(2) << "InsertEntry: entry larger than table, removing " + << table_.size() << " entries, of total size " + << current_size_ << " bytes."; + table_.clear(); + current_size_ = 0; + return; + } + ++insert_count_; + size_t insert_limit = size_limit_ - entry_size; + EnsureSizeNoMoreThan(insert_limit); + table_.push_front(entry); + current_size_ += entry_size; + HTTP2_DVLOG(2) << "InsertEntry: current_size_=" << current_size_; + QUICHE_DCHECK_GE(current_size_, entry_size); + QUICHE_DCHECK_LE(current_size_, size_limit_); +} + +const HpackStringPair* HpackDecoderDynamicTable::Lookup(size_t index) const { + if (index < table_.size()) { + return &table_[index]; + } + return nullptr; +} + +void HpackDecoderDynamicTable::EnsureSizeNoMoreThan(size_t limit) { + HTTP2_DVLOG(2) << "EnsureSizeNoMoreThan limit=" << limit + << ", current_size_=" << current_size_; + // Not the most efficient choice, but any easy way to start. + while (current_size_ > limit) { + RemoveLastEntry(); + } + QUICHE_DCHECK_LE(current_size_, limit); +} + +void HpackDecoderDynamicTable::RemoveLastEntry() { + QUICHE_DCHECK(!table_.empty()); + if (!table_.empty()) { + HTTP2_DVLOG(2) << "RemoveLastEntry current_size_=" << current_size_ + << ", last entry size=" << table_.back().size(); + QUICHE_DCHECK_GE(current_size_, table_.back().size()); + current_size_ -= table_.back().size(); + table_.pop_back(); + // Empty IFF current_size_ == 0. + QUICHE_DCHECK_EQ(table_.empty(), current_size_ == 0); + } +} + +HpackDecoderTables::HpackDecoderTables() = default; +HpackDecoderTables::~HpackDecoderTables() = default; + +const HpackStringPair* HpackDecoderTables::Lookup(size_t index) const { + if (index < kFirstDynamicTableIndex) { + return static_table_.Lookup(index); + } else { + return dynamic_table_.Lookup(index - kFirstDynamicTableIndex); + } +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_tables.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_tables.h new file mode 100644 index 00000000000..e78b91d62b5 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_tables.h @@ -0,0 +1,166 @@ +// 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_HPACK_DECODER_HPACK_DECODER_TABLES_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_TABLES_H_ + +// Static and dynamic tables for the HPACK decoder. See: +// http://httpwg.org/specs/rfc7541.html#indexing.tables + +// Note that the Lookup methods return nullptr if the requested index was not +// found. This should be treated as a COMPRESSION error according to the HTTP/2 +// spec, which is a connection level protocol error (i.e. the connection must +// be terminated). See these sections in the two RFCs: +// http://httpwg.org/specs/rfc7541.html#indexed.header.representation +// http://httpwg.org/specs/rfc7541.html#index.address.space +// http://httpwg.org/specs/rfc7540.html#HeaderBlock + +#include <stddef.h> + +#include <cstdint> +#include <iosfwd> +#include <string> +#include <utility> +#include <vector> + +#include "quiche/http2/http2_constants.h" +#include "quiche/common/platform/api/quiche_export.h" +#include "quiche/common/quiche_circular_deque.h" + +namespace http2 { +namespace test { +class HpackDecoderTablesPeer; +} // namespace test + +struct QUICHE_EXPORT_PRIVATE HpackStringPair { + HpackStringPair(std::string name, std::string value); + ~HpackStringPair(); + + // Returns the size of a header entry with this name and value, per the RFC: + // http://httpwg.org/specs/rfc7541.html#calculating.table.size + size_t size() const { return 32 + name.size() + value.size(); } + + std::string DebugString() const; + + const std::string name; + const std::string value; +}; + +QUICHE_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os, + const HpackStringPair& p); + +// See http://httpwg.org/specs/rfc7541.html#static.table.definition for the +// contents, and http://httpwg.org/specs/rfc7541.html#index.address.space for +// info about accessing the static table. +class QUICHE_EXPORT_PRIVATE HpackDecoderStaticTable { + public: + explicit HpackDecoderStaticTable(const std::vector<HpackStringPair>* table); + // Uses a global table shared by all threads. + HpackDecoderStaticTable(); + + // If index is valid, returns a pointer to the entry, otherwise returns + // nullptr. + const HpackStringPair* Lookup(size_t index) const; + + private: + friend class test::HpackDecoderTablesPeer; + const std::vector<HpackStringPair>* const table_; +}; + +// HpackDecoderDynamicTable implements HPACK compression feature "indexed +// headers"; previously sent headers may be referenced later by their index +// in the dynamic table. See these sections of the RFC: +// http://httpwg.org/specs/rfc7541.html#dynamic.table +// http://httpwg.org/specs/rfc7541.html#dynamic.table.management +class QUICHE_EXPORT_PRIVATE HpackDecoderDynamicTable { + public: + HpackDecoderDynamicTable(); + ~HpackDecoderDynamicTable(); + + HpackDecoderDynamicTable(const HpackDecoderDynamicTable&) = delete; + HpackDecoderDynamicTable& operator=(const HpackDecoderDynamicTable&) = delete; + + // Sets a new size limit, received from the peer; performs evictions if + // necessary to ensure that the current size does not exceed the new limit. + // The caller needs to have validated that size_limit does not + // exceed the acknowledged value of SETTINGS_HEADER_TABLE_SIZE. + void DynamicTableSizeUpdate(size_t size_limit); + + // Insert entry if possible. + // If entry is too large to insert, then dynamic table will be empty. + void Insert(std::string name, std::string value); + + // If index is valid, returns a pointer to the entry, otherwise returns + // nullptr. + const HpackStringPair* Lookup(size_t index) const; + + size_t size_limit() const { return size_limit_; } + size_t current_size() const { return current_size_; } + + private: + friend class test::HpackDecoderTablesPeer; + + // Drop older entries to ensure the size is not greater than limit. + void EnsureSizeNoMoreThan(size_t limit); + + // Removes the oldest dynamic table entry. + void RemoveLastEntry(); + + quiche::QuicheCircularDeque<HpackStringPair> table_; + + // The last received DynamicTableSizeUpdate value, initialized to + // SETTINGS_HEADER_TABLE_SIZE. + size_t size_limit_ = Http2SettingsInfo::DefaultHeaderTableSize(); + + size_t current_size_ = 0; + + // insert_count_ and debug_listener_ are used by a QUIC experiment; remove + // when the experiment is done. + size_t insert_count_; +}; + +class QUICHE_EXPORT_PRIVATE HpackDecoderTables { + public: + HpackDecoderTables(); + ~HpackDecoderTables(); + + HpackDecoderTables(const HpackDecoderTables&) = delete; + HpackDecoderTables& operator=(const HpackDecoderTables&) = delete; + + // Sets a new size limit, received from the peer; performs evictions if + // necessary to ensure that the current size does not exceed the new limit. + // The caller needs to have validated that size_limit does not + // exceed the acknowledged value of SETTINGS_HEADER_TABLE_SIZE. + void DynamicTableSizeUpdate(size_t size_limit) { + dynamic_table_.DynamicTableSizeUpdate(size_limit); + } + + // Insert entry if possible. + // If entry is too large to insert, then dynamic table will be empty. + void Insert(std::string name, std::string value) { + dynamic_table_.Insert(std::move(name), std::move(value)); + } + + // If index is valid, returns a pointer to the entry, otherwise returns + // nullptr. + const HpackStringPair* Lookup(size_t index) const; + + // The size limit that the peer (the HPACK encoder) has told the decoder it is + // currently operating with. Defaults to SETTINGS_HEADER_TABLE_SIZE, 4096. + size_t header_table_size_limit() const { return dynamic_table_.size_limit(); } + + // Sum of the sizes of the dynamic table entries. + size_t current_header_table_size() const { + return dynamic_table_.current_size(); + } + + private: + friend class test::HpackDecoderTablesPeer; + HpackDecoderStaticTable static_table_; + HpackDecoderDynamicTable dynamic_table_; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_TABLES_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_tables_test.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_tables_test.cc new file mode 100644 index 00000000000..cd4655ba0fa --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_tables_test.cc @@ -0,0 +1,259 @@ +// 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/hpack/decoder/hpack_decoder_tables.h" + +#include <algorithm> +#include <string> +#include <tuple> +#include <vector> + +#include "quiche/http2/hpack/http2_hpack_constants.h" +#include "quiche/http2/platform/api/http2_logging.h" +#include "quiche/http2/test_tools/http2_random.h" +#include "quiche/http2/tools/random_util.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/common/platform/api/quiche_test_helpers.h" + +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; + +namespace http2 { +namespace test { +class HpackDecoderTablesPeer { + public: + static size_t num_dynamic_entries(const HpackDecoderTables& tables) { + return tables.dynamic_table_.table_.size(); + } +}; + +namespace { +struct StaticEntry { + const char* name; + const char* value; + size_t index; +}; + +std::vector<StaticEntry> MakeSpecStaticEntries() { + std::vector<StaticEntry> static_entries; + +#define STATIC_TABLE_ENTRY(name, value, index) \ + QUICHE_DCHECK_EQ(static_entries.size() + 1, static_cast<size_t>(index)); \ + static_entries.push_back({name, value, index}); + +#include "quiche/http2/hpack/hpack_static_table_entries.inc" + +#undef STATIC_TABLE_ENTRY + + return static_entries; +} + +template <class C> +void ShuffleCollection(C* collection, Http2Random* r) { + std::shuffle(collection->begin(), collection->end(), *r); +} + +class HpackDecoderStaticTableTest : public QuicheTest { + protected: + HpackDecoderStaticTableTest() = default; + + std::vector<StaticEntry> shuffled_static_entries() { + std::vector<StaticEntry> entries = MakeSpecStaticEntries(); + ShuffleCollection(&entries, &random_); + return entries; + } + + // This test is in a function so that it can be applied to both the static + // table and the combined static+dynamic tables. + AssertionResult VerifyStaticTableContents() { + for (const auto& expected : shuffled_static_entries()) { + const HpackStringPair* found = Lookup(expected.index); + VERIFY_NE(found, nullptr); + VERIFY_EQ(expected.name, found->name) << expected.index; + VERIFY_EQ(expected.value, found->value) << expected.index; + } + + // There should be no entry with index 0. + VERIFY_EQ(nullptr, Lookup(0)); + return AssertionSuccess(); + } + + virtual const HpackStringPair* Lookup(size_t index) { + return static_table_.Lookup(index); + } + + Http2Random* RandomPtr() { return &random_; } + + Http2Random random_; + + private: + HpackDecoderStaticTable static_table_; +}; + +TEST_F(HpackDecoderStaticTableTest, StaticTableContents) { + EXPECT_TRUE(VerifyStaticTableContents()); +} + +size_t Size(const std::string& name, const std::string& value) { + return name.size() + value.size() + 32; +} + +// To support tests with more than a few of hand crafted changes to the dynamic +// table, we have another, exceedingly simple, implementation of the HPACK +// dynamic table containing FakeHpackEntry instances. We can thus compare the +// contents of the actual table with those in fake_dynamic_table_. + +typedef std::tuple<std::string, std::string, size_t> FakeHpackEntry; +const std::string& Name(const FakeHpackEntry& entry) { + return std::get<0>(entry); +} +const std::string& Value(const FakeHpackEntry& entry) { + return std::get<1>(entry); +} +size_t Size(const FakeHpackEntry& entry) { + return std::get<2>(entry); +} + +class HpackDecoderTablesTest : public HpackDecoderStaticTableTest { + protected: + const HpackStringPair* Lookup(size_t index) override { + return tables_.Lookup(index); + } + + size_t dynamic_size_limit() const { + return tables_.header_table_size_limit(); + } + size_t current_dynamic_size() const { + return tables_.current_header_table_size(); + } + size_t num_dynamic_entries() const { + return HpackDecoderTablesPeer::num_dynamic_entries(tables_); + } + + // Insert the name and value into fake_dynamic_table_. + void FakeInsert(const std::string& name, const std::string& value) { + FakeHpackEntry entry(name, value, Size(name, value)); + fake_dynamic_table_.insert(fake_dynamic_table_.begin(), entry); + } + + // Add up the size of all entries in fake_dynamic_table_. + size_t FakeSize() { + size_t sz = 0; + for (const auto& entry : fake_dynamic_table_) { + sz += Size(entry); + } + return sz; + } + + // If the total size of the fake_dynamic_table_ is greater than limit, + // keep the first N entries such that those N entries have a size not + // greater than limit, and such that keeping entry N+1 would have a size + // greater than limit. Returns the count of removed bytes. + size_t FakeTrim(size_t limit) { + size_t original_size = FakeSize(); + size_t total_size = 0; + for (size_t ndx = 0; ndx < fake_dynamic_table_.size(); ++ndx) { + total_size += Size(fake_dynamic_table_[ndx]); + if (total_size > limit) { + // Need to get rid of ndx and all following entries. + fake_dynamic_table_.erase(fake_dynamic_table_.begin() + ndx, + fake_dynamic_table_.end()); + return original_size - FakeSize(); + } + } + return 0; + } + + // Verify that the contents of the actual dynamic table match those in + // fake_dynamic_table_. + AssertionResult VerifyDynamicTableContents() { + VERIFY_EQ(current_dynamic_size(), FakeSize()); + VERIFY_EQ(num_dynamic_entries(), fake_dynamic_table_.size()); + + for (size_t ndx = 0; ndx < fake_dynamic_table_.size(); ++ndx) { + const HpackStringPair* found = Lookup(ndx + kFirstDynamicTableIndex); + VERIFY_NE(found, nullptr); + + const auto& expected = fake_dynamic_table_[ndx]; + VERIFY_EQ(Name(expected), found->name); + VERIFY_EQ(Value(expected), found->value); + } + + // Make sure there are no more entries. + VERIFY_EQ(nullptr, + Lookup(fake_dynamic_table_.size() + kFirstDynamicTableIndex)); + return AssertionSuccess(); + } + + // Apply an update to the limit on the maximum size of the dynamic table. + AssertionResult DynamicTableSizeUpdate(size_t size_limit) { + VERIFY_EQ(current_dynamic_size(), FakeSize()); + if (size_limit < current_dynamic_size()) { + // Will need to trim the dynamic table's oldest entries. + tables_.DynamicTableSizeUpdate(size_limit); + FakeTrim(size_limit); + return VerifyDynamicTableContents(); + } + // Shouldn't change the size. + tables_.DynamicTableSizeUpdate(size_limit); + return VerifyDynamicTableContents(); + } + + // Insert an entry into the dynamic table, confirming that trimming of entries + // occurs if the total size is greater than the limit, and that older entries + // move up by 1 index. + AssertionResult Insert(const std::string& name, const std::string& value) { + size_t old_count = num_dynamic_entries(); + tables_.Insert(name, value); + FakeInsert(name, value); + VERIFY_EQ(old_count + 1, fake_dynamic_table_.size()); + FakeTrim(dynamic_size_limit()); + VERIFY_EQ(current_dynamic_size(), FakeSize()); + VERIFY_EQ(num_dynamic_entries(), fake_dynamic_table_.size()); + return VerifyDynamicTableContents(); + } + + private: + HpackDecoderTables tables_; + + std::vector<FakeHpackEntry> fake_dynamic_table_; +}; + +TEST_F(HpackDecoderTablesTest, StaticTableContents) { + EXPECT_TRUE(VerifyStaticTableContents()); +} + +// Generate a bunch of random header entries, insert them, and confirm they +// present, as required by the RFC, using VerifyDynamicTableContents above on +// each Insert. Also apply various resizings of the dynamic table. +TEST_F(HpackDecoderTablesTest, RandomDynamicTable) { + EXPECT_EQ(0u, current_dynamic_size()); + EXPECT_TRUE(VerifyStaticTableContents()); + EXPECT_TRUE(VerifyDynamicTableContents()); + + std::vector<size_t> table_sizes; + table_sizes.push_back(dynamic_size_limit()); + table_sizes.push_back(0); + table_sizes.push_back(dynamic_size_limit() / 2); + table_sizes.push_back(dynamic_size_limit()); + table_sizes.push_back(dynamic_size_limit() / 2); + table_sizes.push_back(0); + table_sizes.push_back(dynamic_size_limit()); + + for (size_t limit : table_sizes) { + ASSERT_TRUE(DynamicTableSizeUpdate(limit)); + for (int insert_count = 0; insert_count < 100; ++insert_count) { + std::string name = + GenerateHttp2HeaderName(random_.UniformInRange(2, 40), RandomPtr()); + std::string value = + GenerateWebSafeString(random_.UniformInRange(2, 600), RandomPtr()); + ASSERT_TRUE(Insert(name, value)); + } + EXPECT_TRUE(VerifyStaticTableContents()); + } +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_test.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_test.cc new file mode 100644 index 00000000000..18b7b64746a --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_test.cc @@ -0,0 +1,1192 @@ +// 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/hpack/decoder/hpack_decoder.h" + +// Tests of HpackDecoder. + +#include <string> +#include <tuple> +#include <utility> +#include <vector> + +#include "quiche/http2/decoder/decode_buffer.h" +#include "quiche/http2/hpack/decoder/hpack_decoder_listener.h" +#include "quiche/http2/hpack/decoder/hpack_decoder_state.h" +#include "quiche/http2/hpack/decoder/hpack_decoder_tables.h" +#include "quiche/http2/hpack/http2_hpack_constants.h" +#include "quiche/http2/hpack/tools/hpack_block_builder.h" +#include "quiche/http2/hpack/tools/hpack_example.h" +#include "quiche/http2/http2_constants.h" +#include "quiche/http2/platform/api/http2_logging.h" +#include "quiche/http2/test_tools/http2_random.h" +#include "quiche/http2/tools/random_util.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/common/platform/api/quiche_test_helpers.h" + +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using ::testing::ElementsAreArray; +using ::testing::Eq; + +namespace http2 { +namespace test { +class HpackDecoderStatePeer { + public: + static HpackDecoderTables* GetDecoderTables(HpackDecoderState* state) { + return &state->decoder_tables_; + } + static void set_listener(HpackDecoderState* state, + HpackDecoderListener* listener) { + state->listener_ = listener; + } +}; +class HpackDecoderPeer { + public: + static HpackDecoderState* GetDecoderState(HpackDecoder* decoder) { + return &decoder->decoder_state_; + } + static HpackDecoderTables* GetDecoderTables(HpackDecoder* decoder) { + return HpackDecoderStatePeer::GetDecoderTables(GetDecoderState(decoder)); + } +}; + +namespace { + +typedef std::pair<std::string, std::string> HpackHeaderEntry; +typedef std::vector<HpackHeaderEntry> HpackHeaderEntries; + +// TODO(jamessynge): Create a ...test_utils.h file with the mock listener +// and with VerifyDynamicTableContents. +class MockHpackDecoderListener : public HpackDecoderListener { + public: + MOCK_METHOD(void, OnHeaderListStart, (), (override)); + MOCK_METHOD(void, + OnHeader, + (const std::string& name, const std::string& value), + (override)); + MOCK_METHOD(void, OnHeaderListEnd, (), (override)); + MOCK_METHOD(void, + OnHeaderErrorDetected, + (absl::string_view error_message), + (override)); +}; + +class HpackDecoderTest : public QuicheTestWithParam<bool>, + public HpackDecoderListener { + protected: + // Note that we initialize the random number generator with the same seed + // for each individual test, therefore the order in which the tests are + // executed does not effect the sequence produced by the RNG within any + // one test. + HpackDecoderTest() : decoder_(this, 4096) { + fragment_the_hpack_block_ = GetParam(); + } + ~HpackDecoderTest() override = default; + + void OnHeaderListStart() override { + ASSERT_FALSE(saw_start_); + ASSERT_FALSE(saw_end_); + saw_start_ = true; + header_entries_.clear(); + } + + // Called for each header name-value pair that is decoded, in the order they + // appear in the HPACK block. Multiple values for a given key will be emitted + // as multiple calls to OnHeader. + void OnHeader(const std::string& name, const std::string& value) override { + ASSERT_TRUE(saw_start_); + ASSERT_FALSE(saw_end_); + header_entries_.emplace_back(name, value); + } + + // OnHeaderBlockEnd is called after successfully decoding an HPACK block. Will + // only be called once per block, even if it extends into CONTINUATION frames. + // A callback method which notifies when the parser finishes handling a + // header block (i.e. the containing frame has the END_STREAM flag set). + // Also indicates the total number of bytes in this block. + void OnHeaderListEnd() override { + ASSERT_TRUE(saw_start_); + ASSERT_FALSE(saw_end_); + ASSERT_TRUE(error_messages_.empty()); + saw_end_ = true; + } + + // OnHeaderErrorDetected is called if an error is detected while decoding. + // error_message may be used in a GOAWAY frame as the Opaque Data. + void OnHeaderErrorDetected(absl::string_view error_message) override { + ASSERT_TRUE(saw_start_); + error_messages_.push_back(std::string(error_message)); + // No further callbacks should be made at this point, so replace 'this' as + // the listener with mock_listener_, which is a strict mock, so will + // generate an error for any calls. + HpackDecoderStatePeer::set_listener( + HpackDecoderPeer::GetDecoderState(&decoder_), &mock_listener_); + } + + AssertionResult DecodeBlock(absl::string_view block) { + HTTP2_VLOG(1) << "HpackDecoderTest::DecodeBlock"; + + VERIFY_FALSE(decoder_.DetectError()); + VERIFY_TRUE(error_messages_.empty()); + VERIFY_FALSE(saw_start_); + VERIFY_FALSE(saw_end_); + header_entries_.clear(); + + VERIFY_FALSE(decoder_.DetectError()); + VERIFY_TRUE(decoder_.StartDecodingBlock()); + VERIFY_FALSE(decoder_.DetectError()); + + if (fragment_the_hpack_block_) { + // See note in ctor regarding RNG. + while (!block.empty()) { + size_t fragment_size = random_.RandomSizeSkewedLow(block.size()); + DecodeBuffer db(block.substr(0, fragment_size)); + VERIFY_TRUE(decoder_.DecodeFragment(&db)); + VERIFY_EQ(0u, db.Remaining()); + block.remove_prefix(fragment_size); + } + } else { + DecodeBuffer db(block); + VERIFY_TRUE(decoder_.DecodeFragment(&db)); + VERIFY_EQ(0u, db.Remaining()); + } + VERIFY_FALSE(decoder_.DetectError()); + + VERIFY_TRUE(decoder_.EndDecodingBlock()); + if (saw_end_) { + VERIFY_FALSE(decoder_.DetectError()); + VERIFY_TRUE(error_messages_.empty()); + } else { + VERIFY_TRUE(decoder_.DetectError()); + VERIFY_FALSE(error_messages_.empty()); + } + + saw_start_ = saw_end_ = false; + return AssertionSuccess(); + } + + const HpackDecoderTables& GetDecoderTables() { + return *HpackDecoderPeer::GetDecoderTables(&decoder_); + } + const HpackStringPair* Lookup(size_t index) { + return GetDecoderTables().Lookup(index); + } + size_t current_header_table_size() { + return GetDecoderTables().current_header_table_size(); + } + size_t header_table_size_limit() { + return GetDecoderTables().header_table_size_limit(); + } + void set_header_table_size_limit(size_t size) { + HpackDecoderPeer::GetDecoderTables(&decoder_)->DynamicTableSizeUpdate(size); + } + + // dynamic_index is one-based, because that is the way RFC 7541 shows it. + AssertionResult VerifyEntry(size_t dynamic_index, + const char* name, + const char* value) { + const HpackStringPair* entry = + Lookup(dynamic_index + kFirstDynamicTableIndex - 1); + VERIFY_NE(entry, nullptr); + VERIFY_EQ(entry->name, name); + VERIFY_EQ(entry->value, value); + return AssertionSuccess(); + } + AssertionResult VerifyNoEntry(size_t dynamic_index) { + const HpackStringPair* entry = + Lookup(dynamic_index + kFirstDynamicTableIndex - 1); + VERIFY_EQ(entry, nullptr); + return AssertionSuccess(); + } + AssertionResult VerifyDynamicTableContents( + const std::vector<std::pair<const char*, const char*>>& entries) { + size_t index = 1; + for (const auto& entry : entries) { + VERIFY_SUCCESS(VerifyEntry(index, entry.first, entry.second)); + ++index; + } + VERIFY_SUCCESS(VerifyNoEntry(index)); + return AssertionSuccess(); + } + + Http2Random random_; + HpackDecoder decoder_; + testing::StrictMock<MockHpackDecoderListener> mock_listener_; + HpackHeaderEntries header_entries_; + std::vector<std::string> error_messages_; + bool fragment_the_hpack_block_; + bool saw_start_ = false; + bool saw_end_ = false; +}; +INSTANTIATE_TEST_SUITE_P(AllWays, HpackDecoderTest, ::testing::Bool()); + +// Test based on RFC 7541, section C.3: Request Examples without Huffman Coding. +// This section shows several consecutive header lists, corresponding to HTTP +// requests, on the same connection. +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.3 +TEST_P(HpackDecoderTest, C3_RequestExamples) { + // C.3.1 First Request + std::string hpack_block = HpackExampleToStringOrDie(R"( + 82 | == Indexed - Add == + | idx = 2 + | -> :method: GET + 86 | == Indexed - Add == + | idx = 6 + | -> :scheme: http + 84 | == Indexed - Add == + | idx = 4 + | -> :path: / + 41 | == Literal indexed == + | Indexed name (idx = 1) + | :authority + 0f | Literal value (len = 15) + 7777 772e 6578 616d 706c 652e 636f 6d | www.example.com + | -> :authority: + | www.example.com + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT(header_entries_, + ElementsAreArray({ + HpackHeaderEntry{":method", "GET"}, + HpackHeaderEntry{":scheme", "http"}, + HpackHeaderEntry{":path", "/"}, + HpackHeaderEntry{":authority", "www.example.com"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 57) :authority: www.example.com + // Table size: 57 + ASSERT_TRUE(VerifyDynamicTableContents({{":authority", "www.example.com"}})); + ASSERT_EQ(57u, current_header_table_size()); + + // C.3.2 Second Request + hpack_block = HpackExampleToStringOrDie(R"( + 82 | == Indexed - Add == + | idx = 2 + | -> :method: GET + 86 | == Indexed - Add == + | idx = 6 + | -> :scheme: http + 84 | == Indexed - Add == + | idx = 4 + | -> :path: / + be | == Indexed - Add == + | idx = 62 + | -> :authority: + | www.example.com + 58 | == Literal indexed == + | Indexed name (idx = 24) + | cache-control + 08 | Literal value (len = 8) + 6e6f 2d63 6163 6865 | no-cache + | -> cache-control: no-cache + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT(header_entries_, + ElementsAreArray({ + HpackHeaderEntry{":method", "GET"}, + HpackHeaderEntry{":scheme", "http"}, + HpackHeaderEntry{":path", "/"}, + HpackHeaderEntry{":authority", "www.example.com"}, + HpackHeaderEntry{"cache-control", "no-cache"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 53) cache-control: no-cache + // [ 2] (s = 57) :authority: www.example.com + // Table size: 110 + ASSERT_TRUE(VerifyDynamicTableContents( + {{"cache-control", "no-cache"}, {":authority", "www.example.com"}})); + ASSERT_EQ(110u, current_header_table_size()); + + // C.3.2 Third Request + hpack_block = HpackExampleToStringOrDie(R"( + 82 | == Indexed - Add == + | idx = 2 + | -> :method: GET + 87 | == Indexed - Add == + | idx = 7 + | -> :scheme: https + 85 | == Indexed - Add == + | idx = 5 + | -> :path: /index.html + bf | == Indexed - Add == + | idx = 63 + | -> :authority: + | www.example.com + 40 | == Literal indexed == + 0a | Literal name (len = 10) + 6375 7374 6f6d 2d6b 6579 | custom-key + 0c | Literal value (len = 12) + 6375 7374 6f6d 2d76 616c 7565 | custom-value + | -> custom-key: + | custom-value + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT(header_entries_, + ElementsAreArray({ + HpackHeaderEntry{":method", "GET"}, + HpackHeaderEntry{":scheme", "https"}, + HpackHeaderEntry{":path", "/index.html"}, + HpackHeaderEntry{":authority", "www.example.com"}, + HpackHeaderEntry{"custom-key", "custom-value"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 54) custom-key: custom-value + // [ 2] (s = 53) cache-control: no-cache + // [ 3] (s = 57) :authority: www.example.com + // Table size: 164 + ASSERT_TRUE(VerifyDynamicTableContents({{"custom-key", "custom-value"}, + {"cache-control", "no-cache"}, + {":authority", "www.example.com"}})); + ASSERT_EQ(164u, current_header_table_size()); +} + +// Test based on RFC 7541, section C.4 Request Examples with Huffman Coding. +// This section shows the same examples as the previous section but uses +// Huffman encoding for the literal values. +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.4 +TEST_P(HpackDecoderTest, C4_RequestExamplesWithHuffmanEncoding) { + // C.4.1 First Request + std::string hpack_block = HpackExampleToStringOrDie(R"( + 82 | == Indexed - Add == + | idx = 2 + | -> :method: GET + 86 | == Indexed - Add == + | idx = 6 + | -> :scheme: http + 84 | == Indexed - Add == + | idx = 4 + | -> :path: / + 41 | == Literal indexed == + | Indexed name (idx = 1) + | :authority + 8c | Literal value (len = 12) + | Huffman encoded: + f1e3 c2e5 f23a 6ba0 ab90 f4ff | .....:k..... + | Decoded: + | www.example.com + | -> :authority: + | www.example.com + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT(header_entries_, + ElementsAreArray({ + HpackHeaderEntry{":method", "GET"}, + HpackHeaderEntry{":scheme", "http"}, + HpackHeaderEntry{":path", "/"}, + HpackHeaderEntry{":authority", "www.example.com"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 57) :authority: www.example.com + // Table size: 57 + ASSERT_TRUE(VerifyDynamicTableContents({{":authority", "www.example.com"}})); + ASSERT_EQ(57u, current_header_table_size()); + + // C.4.2 Second Request + hpack_block = HpackExampleToStringOrDie(R"( + 82 | == Indexed - Add == + | idx = 2 + | -> :method: GET + 86 | == Indexed - Add == + | idx = 6 + | -> :scheme: http + 84 | == Indexed - Add == + | idx = 4 + | -> :path: / + be | == Indexed - Add == + | idx = 62 + | -> :authority: + | www.example.com + 58 | == Literal indexed == + | Indexed name (idx = 24) + | cache-control + 86 | Literal value (len = 6) + | Huffman encoded: + a8eb 1064 9cbf | ...d.. + | Decoded: + | no-cache + | -> cache-control: no-cache + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT(header_entries_, + ElementsAreArray({ + HpackHeaderEntry{":method", "GET"}, + HpackHeaderEntry{":scheme", "http"}, + HpackHeaderEntry{":path", "/"}, + HpackHeaderEntry{":authority", "www.example.com"}, + HpackHeaderEntry{"cache-control", "no-cache"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 53) cache-control: no-cache + // [ 2] (s = 57) :authority: www.example.com + // Table size: 110 + ASSERT_TRUE(VerifyDynamicTableContents( + {{"cache-control", "no-cache"}, {":authority", "www.example.com"}})); + ASSERT_EQ(110u, current_header_table_size()); + + // C.4.2 Third Request + hpack_block = HpackExampleToStringOrDie(R"( + 82 | == Indexed - Add == + | idx = 2 + | -> :method: GET + 87 | == Indexed - Add == + | idx = 7 + | -> :scheme: https + 85 | == Indexed - Add == + | idx = 5 + | -> :path: /index.html + bf | == Indexed - Add == + | idx = 63 + | -> :authority: + | www.example.com + 40 | == Literal indexed == + 88 | Literal name (len = 8) + | Huffman encoded: + 25a8 49e9 5ba9 7d7f | %.I.[.}. + | Decoded: + | custom-key + 89 | Literal value (len = 9) + | Huffman encoded: + 25a8 49e9 5bb8 e8b4 bf | %.I.[.... + | Decoded: + | custom-value + | -> custom-key: + | custom-value + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT(header_entries_, + ElementsAreArray({ + HpackHeaderEntry{":method", "GET"}, + HpackHeaderEntry{":scheme", "https"}, + HpackHeaderEntry{":path", "/index.html"}, + HpackHeaderEntry{":authority", "www.example.com"}, + HpackHeaderEntry{"custom-key", "custom-value"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 54) custom-key: custom-value + // [ 2] (s = 53) cache-control: no-cache + // [ 3] (s = 57) :authority: www.example.com + // Table size: 164 + ASSERT_TRUE(VerifyDynamicTableContents({{"custom-key", "custom-value"}, + {"cache-control", "no-cache"}, + {":authority", "www.example.com"}})); + ASSERT_EQ(164u, current_header_table_size()); +} + +// Test based on RFC 7541, section C.5: Response Examples without Huffman +// Coding. This section shows several consecutive header lists, corresponding +// to HTTP responses, on the same connection. The HTTP/2 setting parameter +// SETTINGS_HEADER_TABLE_SIZE is set to the value of 256 octets, causing +// some evictions to occur. +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.5 +TEST_P(HpackDecoderTest, C5_ResponseExamples) { + set_header_table_size_limit(256); + + // C.5.1 First Response + // + // Header list to encode: + // + // :status: 302 + // cache-control: private + // date: Mon, 21 Oct 2013 20:13:21 GMT + // location: https://www.example.com + + std::string hpack_block = HpackExampleToStringOrDie(R"( + 48 | == Literal indexed == + | Indexed name (idx = 8) + | :status + 03 | Literal value (len = 3) + 3330 32 | 302 + | -> :status: 302 + 58 | == Literal indexed == + | Indexed name (idx = 24) + | cache-control + 07 | Literal value (len = 7) + 7072 6976 6174 65 | private + | -> cache-control: private + 61 | == Literal indexed == + | Indexed name (idx = 33) + | date + 1d | Literal value (len = 29) + 4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013 + 2032 303a 3133 3a32 3120 474d 54 | 20:13:21 GMT + | -> date: Mon, 21 Oct 2013 + | 20:13:21 GMT + 6e | == Literal indexed == + | Indexed name (idx = 46) + | location + 17 | Literal value (len = 23) + 6874 7470 733a 2f2f 7777 772e 6578 616d | https://www.exam + 706c 652e 636f 6d | ple.com + | -> location: + | https://www.example.com + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT(header_entries_, + ElementsAreArray({ + HpackHeaderEntry{":status", "302"}, + HpackHeaderEntry{"cache-control", "private"}, + HpackHeaderEntry{"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + HpackHeaderEntry{"location", "https://www.example.com"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 63) location: https://www.example.com + // [ 2] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT + // [ 3] (s = 52) cache-control: private + // [ 4] (s = 42) :status: 302 + // Table size: 222 + ASSERT_TRUE( + VerifyDynamicTableContents({{"location", "https://www.example.com"}, + {"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + {"cache-control", "private"}, + {":status", "302"}})); + ASSERT_EQ(222u, current_header_table_size()); + + // C.5.2 Second Response + // + // The (":status", "302") header field is evicted from the dynamic table to + // free space to allow adding the (":status", "307") header field. + // + // Header list to encode: + // + // :status: 307 + // cache-control: private + // date: Mon, 21 Oct 2013 20:13:21 GMT + // location: https://www.example.com + + hpack_block = HpackExampleToStringOrDie(R"( + 48 | == Literal indexed == + | Indexed name (idx = 8) + | :status + 03 | Literal value (len = 3) + 3330 37 | 307 + | - evict: :status: 302 + | -> :status: 307 + c1 | == Indexed - Add == + | idx = 65 + | -> cache-control: private + c0 | == Indexed - Add == + | idx = 64 + | -> date: Mon, 21 Oct 2013 + | 20:13:21 GMT + bf | == Indexed - Add == + | idx = 63 + | -> location: + | https://www.example.com + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT(header_entries_, + ElementsAreArray({ + HpackHeaderEntry{":status", "307"}, + HpackHeaderEntry{"cache-control", "private"}, + HpackHeaderEntry{"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + HpackHeaderEntry{"location", "https://www.example.com"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 42) :status: 307 + // [ 2] (s = 63) location: https://www.example.com + // [ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT + // [ 4] (s = 52) cache-control: private + // Table size: 222 + + ASSERT_TRUE( + VerifyDynamicTableContents({{":status", "307"}, + {"location", "https://www.example.com"}, + {"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + {"cache-control", "private"}})); + ASSERT_EQ(222u, current_header_table_size()); + + // C.5.3 Third Response + // + // Several header fields are evicted from the dynamic table during the + // processing of this header list. + // + // Header list to encode: + // + // :status: 200 + // cache-control: private + // date: Mon, 21 Oct 2013 20:13:22 GMT + // location: https://www.example.com + // content-encoding: gzip + // set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1 + hpack_block = HpackExampleToStringOrDie(R"( + 88 | == Indexed - Add == + | idx = 8 + | -> :status: 200 + c1 | == Indexed - Add == + | idx = 65 + | -> cache-control: private + 61 | == Literal indexed == + | Indexed name (idx = 33) + | date + 1d | Literal value (len = 29) + 4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013 + 2032 303a 3133 3a32 3220 474d 54 | 20:13:22 GMT + | - evict: cache-control: + | private + | -> date: Mon, 21 Oct 2013 + | 20:13:22 GMT + c0 | == Indexed - Add == + | idx = 64 + | -> location: + | https://www.example.com + 5a | == Literal indexed == + | Indexed name (idx = 26) + | content-encoding + 04 | Literal value (len = 4) + 677a 6970 | gzip + | - evict: date: Mon, 21 Oct + | 2013 20:13:21 GMT + | -> content-encoding: gzip + 77 | == Literal indexed == + | Indexed name (idx = 55) + | set-cookie + 38 | Literal value (len = 56) + 666f 6f3d 4153 444a 4b48 514b 425a 584f | foo=ASDJKHQKBZXO + 5157 454f 5049 5541 5851 5745 4f49 553b | QWEOPIUAXQWEOIU; + 206d 6178 2d61 6765 3d33 3630 303b 2076 | max-age=3600; v + 6572 7369 6f6e 3d31 | ersion=1 + | - evict: location: + | https://www.example.com + | - evict: :status: 307 + | -> set-cookie: foo=ASDJKHQ + | KBZXOQWEOPIUAXQWEOIU; ma + | x-age=3600; version=1 + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT( + header_entries_, + ElementsAreArray({ + HpackHeaderEntry{":status", "200"}, + HpackHeaderEntry{"cache-control", "private"}, + HpackHeaderEntry{"date", "Mon, 21 Oct 2013 20:13:22 GMT"}, + HpackHeaderEntry{"location", "https://www.example.com"}, + HpackHeaderEntry{"content-encoding", "gzip"}, + HpackHeaderEntry{ + "set-cookie", + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; + // max-age=3600; version=1 + // [ 2] (s = 52) content-encoding: gzip + // [ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:22 GMT + // Table size: 215 + ASSERT_TRUE(VerifyDynamicTableContents( + {{"set-cookie", + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"}, + {"content-encoding", "gzip"}, + {"date", "Mon, 21 Oct 2013 20:13:22 GMT"}})); + ASSERT_EQ(215u, current_header_table_size()); +} + +// Test based on RFC 7541, section C.6: Response Examples with Huffman Coding. +// This section shows the same examples as the previous section but uses Huffman +// encoding for the literal values. The HTTP/2 setting parameter +// SETTINGS_HEADER_TABLE_SIZE is set to the value of 256 octets, causing some +// evictions to occur. The eviction mechanism uses the length of the decoded +// literal values, so the same evictions occur as in the previous section. +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.6 +TEST_P(HpackDecoderTest, C6_ResponseExamplesWithHuffmanEncoding) { + set_header_table_size_limit(256); + + // C.5.1 First Response + // + // Header list to encode: + // + // :status: 302 + // cache-control: private + // date: Mon, 21 Oct 2013 20:13:21 GMT + // location: https://www.example.com + std::string hpack_block = HpackExampleToStringOrDie(R"( + 48 | == Literal indexed == + | Indexed name (idx = 8) + | :status + 03 | Literal value (len = 3) + 3330 32 | 302 + | -> :status: 302 + 58 | == Literal indexed == + | Indexed name (idx = 24) + | cache-control + 07 | Literal value (len = 7) + 7072 6976 6174 65 | private + | -> cache-control: private + 61 | == Literal indexed == + | Indexed name (idx = 33) + | date + 1d | Literal value (len = 29) + 4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013 + 2032 303a 3133 3a32 3120 474d 54 | 20:13:21 GMT + | -> date: Mon, 21 Oct 2013 + | 20:13:21 GMT + 6e | == Literal indexed == + | Indexed name (idx = 46) + | location + 17 | Literal value (len = 23) + 6874 7470 733a 2f2f 7777 772e 6578 616d | https://www.exam + 706c 652e 636f 6d | ple.com + | -> location: + | https://www.example.com + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT(header_entries_, + ElementsAreArray({ + HpackHeaderEntry{":status", "302"}, + HpackHeaderEntry{"cache-control", "private"}, + HpackHeaderEntry{"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + HpackHeaderEntry{"location", "https://www.example.com"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 63) location: https://www.example.com + // [ 2] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT + // [ 3] (s = 52) cache-control: private + // [ 4] (s = 42) :status: 302 + // Table size: 222 + ASSERT_TRUE( + VerifyDynamicTableContents({{"location", "https://www.example.com"}, + {"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + {"cache-control", "private"}, + {":status", "302"}})); + ASSERT_EQ(222u, current_header_table_size()); + + // C.5.2 Second Response + // + // The (":status", "302") header field is evicted from the dynamic table to + // free space to allow adding the (":status", "307") header field. + // + // Header list to encode: + // + // :status: 307 + // cache-control: private + // date: Mon, 21 Oct 2013 20:13:21 GMT + // location: https://www.example.com + hpack_block = HpackExampleToStringOrDie(R"( + 48 | == Literal indexed == + | Indexed name (idx = 8) + | :status + 03 | Literal value (len = 3) + 3330 37 | 307 + | - evict: :status: 302 + | -> :status: 307 + c1 | == Indexed - Add == + | idx = 65 + | -> cache-control: private + c0 | == Indexed - Add == + | idx = 64 + | -> date: Mon, 21 Oct 2013 + | 20:13:21 GMT + bf | == Indexed - Add == + | idx = 63 + | -> location: + | https://www.example.com + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT(header_entries_, + ElementsAreArray({ + HpackHeaderEntry{":status", "307"}, + HpackHeaderEntry{"cache-control", "private"}, + HpackHeaderEntry{"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + HpackHeaderEntry{"location", "https://www.example.com"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 42) :status: 307 + // [ 2] (s = 63) location: https://www.example.com + // [ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT + // [ 4] (s = 52) cache-control: private + // Table size: 222 + ASSERT_TRUE( + VerifyDynamicTableContents({{":status", "307"}, + {"location", "https://www.example.com"}, + {"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + {"cache-control", "private"}})); + ASSERT_EQ(222u, current_header_table_size()); + + // C.5.3 Third Response + // + // Several header fields are evicted from the dynamic table during the + // processing of this header list. + // + // Header list to encode: + // + // :status: 200 + // cache-control: private + // date: Mon, 21 Oct 2013 20:13:22 GMT + // location: https://www.example.com + // content-encoding: gzip + // set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1 + hpack_block = HpackExampleToStringOrDie(R"( + 88 | == Indexed - Add == + | idx = 8 + | -> :status: 200 + c1 | == Indexed - Add == + | idx = 65 + | -> cache-control: private + 61 | == Literal indexed == + | Indexed name (idx = 33) + | date + 1d | Literal value (len = 29) + 4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013 + 2032 303a 3133 3a32 3220 474d 54 | 20:13:22 GMT + | - evict: cache-control: + | private + | -> date: Mon, 21 Oct 2013 + | 20:13:22 GMT + c0 | == Indexed - Add == + | idx = 64 + | -> location: + | https://www.example.com + 5a | == Literal indexed == + | Indexed name (idx = 26) + | content-encoding + 04 | Literal value (len = 4) + 677a 6970 | gzip + | - evict: date: Mon, 21 Oct + | 2013 20:13:21 GMT + | -> content-encoding: gzip + 77 | == Literal indexed == + | Indexed name (idx = 55) + | set-cookie + 38 | Literal value (len = 56) + 666f 6f3d 4153 444a 4b48 514b 425a 584f | foo=ASDJKHQKBZXO + 5157 454f 5049 5541 5851 5745 4f49 553b | QWEOPIUAXQWEOIU; + 206d 6178 2d61 6765 3d33 3630 303b 2076 | max-age=3600; v + 6572 7369 6f6e 3d31 | ersion=1 + | - evict: location: + | https://www.example.com + | - evict: :status: 307 + | -> set-cookie: foo=ASDJKHQ + | KBZXOQWEOPIUAXQWEOIU; ma + | x-age=3600; version=1 + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT( + header_entries_, + ElementsAreArray({ + HpackHeaderEntry{":status", "200"}, + HpackHeaderEntry{"cache-control", "private"}, + HpackHeaderEntry{"date", "Mon, 21 Oct 2013 20:13:22 GMT"}, + HpackHeaderEntry{"location", "https://www.example.com"}, + HpackHeaderEntry{"content-encoding", "gzip"}, + HpackHeaderEntry{ + "set-cookie", + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; + // max-age=3600; version=1 + // [ 2] (s = 52) content-encoding: gzip + // [ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:22 GMT + // Table size: 215 + ASSERT_TRUE(VerifyDynamicTableContents( + {{"set-cookie", + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"}, + {"content-encoding", "gzip"}, + {"date", "Mon, 21 Oct 2013 20:13:22 GMT"}})); + ASSERT_EQ(215u, current_header_table_size()); +} + +// Confirm that the table size can be changed, but at most twice. +TEST_P(HpackDecoderTest, ProcessesOptionalTableSizeUpdates) { + EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(), + header_table_size_limit()); + // One update allowed. + { + HpackBlockBuilder hbb; + hbb.AppendDynamicTableSizeUpdate(3000); + EXPECT_TRUE(DecodeBlock(hbb.buffer())); + EXPECT_EQ(3000u, header_table_size_limit()); + EXPECT_EQ(0u, current_header_table_size()); + EXPECT_TRUE(header_entries_.empty()); + } + // Two updates allowed. + { + HpackBlockBuilder hbb; + hbb.AppendDynamicTableSizeUpdate(2000); + hbb.AppendDynamicTableSizeUpdate(2500); + EXPECT_TRUE(DecodeBlock(hbb.buffer())); + EXPECT_EQ(2500u, header_table_size_limit()); + EXPECT_EQ(0u, current_header_table_size()); + EXPECT_TRUE(header_entries_.empty()); + } + // A third update in the same HPACK block is rejected, so the final + // size is 1000, not 500. + { + HpackBlockBuilder hbb; + hbb.AppendDynamicTableSizeUpdate(1500); + hbb.AppendDynamicTableSizeUpdate(1000); + hbb.AppendDynamicTableSizeUpdate(500); + EXPECT_FALSE(DecodeBlock(hbb.buffer())); + EXPECT_EQ(HpackDecodingError::kDynamicTableSizeUpdateNotAllowed, + decoder_.error()); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], + Eq("Dynamic table size update not allowed")); + EXPECT_EQ(1000u, header_table_size_limit()); + EXPECT_EQ(0u, current_header_table_size()); + EXPECT_TRUE(header_entries_.empty()); + } + // An error has been detected, so calls to HpackDecoder::DecodeFragment + // should return immediately. + DecodeBuffer db("\x80"); + EXPECT_FALSE(decoder_.DecodeFragment(&db)); + EXPECT_EQ(0u, db.Offset()); + EXPECT_EQ(1u, error_messages_.size()); +} + +// Confirm that the table size can be changed when required, but at most twice. +TEST_P(HpackDecoderTest, ProcessesRequiredTableSizeUpdate) { + EXPECT_EQ(4096u, decoder_.GetCurrentHeaderTableSizeSetting()); + // One update required, two allowed, one provided, followed by a header. + decoder_.ApplyHeaderTableSizeSetting(1024); + decoder_.ApplyHeaderTableSizeSetting(2048); + EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(), + header_table_size_limit()); + EXPECT_EQ(2048u, decoder_.GetCurrentHeaderTableSizeSetting()); + { + HpackBlockBuilder hbb; + hbb.AppendDynamicTableSizeUpdate(1024); + hbb.AppendIndexedHeader(4); // :path: / + EXPECT_TRUE(DecodeBlock(hbb.buffer())); + EXPECT_THAT(header_entries_, + ElementsAreArray({HpackHeaderEntry{":path", "/"}})); + EXPECT_EQ(1024u, header_table_size_limit()); + EXPECT_EQ(0u, current_header_table_size()); + } + // One update required, two allowed, two provided, followed by a header. + decoder_.ApplyHeaderTableSizeSetting(1000); + decoder_.ApplyHeaderTableSizeSetting(1500); + EXPECT_EQ(1500u, decoder_.GetCurrentHeaderTableSizeSetting()); + { + HpackBlockBuilder hbb; + hbb.AppendDynamicTableSizeUpdate(500); + hbb.AppendDynamicTableSizeUpdate(1250); + hbb.AppendIndexedHeader(5); // :path: /index.html + EXPECT_TRUE(DecodeBlock(hbb.buffer())); + EXPECT_THAT(header_entries_, + ElementsAreArray({HpackHeaderEntry{":path", "/index.html"}})); + EXPECT_EQ(1250u, header_table_size_limit()); + EXPECT_EQ(0u, current_header_table_size()); + } + // One update required, two allowed, three provided, followed by a header. + // The third update is rejected, so the final size is 1000, not 500. + decoder_.ApplyHeaderTableSizeSetting(500); + decoder_.ApplyHeaderTableSizeSetting(1000); + EXPECT_EQ(1000u, decoder_.GetCurrentHeaderTableSizeSetting()); + { + HpackBlockBuilder hbb; + hbb.AppendDynamicTableSizeUpdate(200); + hbb.AppendDynamicTableSizeUpdate(700); + hbb.AppendDynamicTableSizeUpdate(900); + hbb.AppendIndexedHeader(5); // Not decoded. + EXPECT_FALSE(DecodeBlock(hbb.buffer())); + EXPECT_FALSE(saw_end_); + EXPECT_EQ(HpackDecodingError::kDynamicTableSizeUpdateNotAllowed, + decoder_.error()); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], + Eq("Dynamic table size update not allowed")); + EXPECT_EQ(700u, header_table_size_limit()); + EXPECT_EQ(0u, current_header_table_size()); + EXPECT_TRUE(header_entries_.empty()); + } + EXPECT_EQ(1000u, decoder_.GetCurrentHeaderTableSizeSetting()); + // Now that an error has been detected, StartDecodingBlock should return + // false. + EXPECT_FALSE(decoder_.StartDecodingBlock()); +} + +// Confirm that required size updates are validated. +TEST_P(HpackDecoderTest, InvalidRequiredSizeUpdate) { + // Require a size update, but provide one that isn't small enough (must be + // zero or one, in this case). + decoder_.ApplyHeaderTableSizeSetting(1); + decoder_.ApplyHeaderTableSizeSetting(1024); + HpackBlockBuilder hbb; + hbb.AppendDynamicTableSizeUpdate(2); + EXPECT_TRUE(decoder_.StartDecodingBlock()); + DecodeBuffer db(hbb.buffer()); + EXPECT_FALSE(decoder_.DecodeFragment(&db)); + EXPECT_FALSE(saw_end_); + EXPECT_EQ( + HpackDecodingError::kInitialDynamicTableSizeUpdateIsAboveLowWaterMark, + decoder_.error()); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], + Eq("Initial dynamic table size update is above low water mark")); + EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(), + header_table_size_limit()); +} + +// Confirm that required size updates are indeed required before the end. +TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeEnd) { + decoder_.ApplyHeaderTableSizeSetting(1024); + EXPECT_FALSE(DecodeBlock("")); + EXPECT_EQ(HpackDecodingError::kMissingDynamicTableSizeUpdate, + decoder_.error()); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], Eq("Missing dynamic table size update")); + EXPECT_FALSE(saw_end_); +} + +// Confirm that required size updates are indeed required before an +// indexed header. +TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeIndexedHeader) { + decoder_.ApplyHeaderTableSizeSetting(1024); + HpackBlockBuilder hbb; + hbb.AppendIndexedHeader(1); + EXPECT_FALSE(DecodeBlock(hbb.buffer())); + EXPECT_EQ(HpackDecodingError::kMissingDynamicTableSizeUpdate, + decoder_.error()); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], Eq("Missing dynamic table size update")); + EXPECT_FALSE(saw_end_); + EXPECT_TRUE(header_entries_.empty()); +} + +// Confirm that required size updates are indeed required before an indexed +// header name. +// TODO(jamessynge): Move some of these to hpack_decoder_state_test.cc. +TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeIndexedHeaderName) { + decoder_.ApplyHeaderTableSizeSetting(1024); + HpackBlockBuilder hbb; + hbb.AppendNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, 2, + false, "PUT"); + EXPECT_FALSE(DecodeBlock(hbb.buffer())); + EXPECT_EQ(HpackDecodingError::kMissingDynamicTableSizeUpdate, + decoder_.error()); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], Eq("Missing dynamic table size update")); + EXPECT_FALSE(saw_end_); + EXPECT_TRUE(header_entries_.empty()); +} + +// Confirm that required size updates are indeed required before a literal +// header name. +TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeLiteralName) { + decoder_.ApplyHeaderTableSizeSetting(1024); + HpackBlockBuilder hbb; + hbb.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader, + false, "name", false, "some data."); + EXPECT_FALSE(DecodeBlock(hbb.buffer())); + EXPECT_EQ(HpackDecodingError::kMissingDynamicTableSizeUpdate, + decoder_.error()); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], Eq("Missing dynamic table size update")); + EXPECT_FALSE(saw_end_); + EXPECT_TRUE(header_entries_.empty()); +} + +// Confirm that an excessively long varint is detected, in this case an +// index of 127, but with lots of additional high-order 0 bits provided, +// too many to be allowed. +TEST_P(HpackDecoderTest, InvalidIndexedHeaderVarint) { + EXPECT_TRUE(decoder_.StartDecodingBlock()); + DecodeBuffer db("\xff\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x00"); + EXPECT_FALSE(decoder_.DecodeFragment(&db)); + EXPECT_TRUE(decoder_.DetectError()); + EXPECT_FALSE(saw_end_); + EXPECT_EQ(HpackDecodingError::kIndexVarintError, decoder_.error()); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], + Eq("Index varint beyond implementation limit")); + EXPECT_TRUE(header_entries_.empty()); + // Now that an error has been detected, EndDecodingBlock should not succeed. + EXPECT_FALSE(decoder_.EndDecodingBlock()); +} + +// Confirm that an invalid index into the tables is detected, in this case an +// index of 0. +TEST_P(HpackDecoderTest, InvalidIndex) { + EXPECT_TRUE(decoder_.StartDecodingBlock()); + DecodeBuffer db("\x80"); + EXPECT_FALSE(decoder_.DecodeFragment(&db)); + EXPECT_TRUE(decoder_.DetectError()); + EXPECT_FALSE(saw_end_); + EXPECT_EQ(HpackDecodingError::kInvalidIndex, decoder_.error()); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], + Eq("Invalid index in indexed header field representation")); + EXPECT_TRUE(header_entries_.empty()); + // Now that an error has been detected, EndDecodingBlock should not succeed. + EXPECT_FALSE(decoder_.EndDecodingBlock()); +} + +// Confirm that EndDecodingBlock detects a truncated HPACK block. +TEST_P(HpackDecoderTest, TruncatedBlock) { + HpackBlockBuilder hbb; + hbb.AppendDynamicTableSizeUpdate(3000); + EXPECT_EQ(3u, hbb.size()); + hbb.AppendDynamicTableSizeUpdate(4000); + EXPECT_EQ(6u, hbb.size()); + // Decodes this block if the whole thing is provided. + EXPECT_TRUE(DecodeBlock(hbb.buffer())); + EXPECT_EQ(4000u, header_table_size_limit()); + // Multiple times even. + EXPECT_TRUE(DecodeBlock(hbb.buffer())); + EXPECT_EQ(4000u, header_table_size_limit()); + // But not if the block is truncated. + EXPECT_FALSE(DecodeBlock(hbb.buffer().substr(0, hbb.size() - 1))); + EXPECT_FALSE(saw_end_); + EXPECT_EQ(HpackDecodingError::kTruncatedBlock, decoder_.error()); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], + Eq("Block ends in the middle of an instruction")); + // The first update was decoded. + EXPECT_EQ(3000u, header_table_size_limit()); + EXPECT_EQ(0u, current_header_table_size()); + EXPECT_TRUE(header_entries_.empty()); +} + +// Confirm that an oversized string is detected, ending decoding. +TEST_P(HpackDecoderTest, OversizeStringDetected) { + HpackBlockBuilder hbb; + hbb.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader, + false, "name", false, "some data."); + hbb.AppendLiteralNameAndValue(HpackEntryType::kUnindexedLiteralHeader, false, + "name2", false, "longer data"); + + // Normally able to decode this block. + EXPECT_TRUE(DecodeBlock(hbb.buffer())); + EXPECT_THAT(header_entries_, + ElementsAreArray({HpackHeaderEntry{"name", "some data."}, + HpackHeaderEntry{"name2", "longer data"}})); + + // But not if the maximum size of strings is less than the longest string. + decoder_.set_max_string_size_bytes(10); + EXPECT_FALSE(DecodeBlock(hbb.buffer())); + EXPECT_THAT(header_entries_, + ElementsAreArray({HpackHeaderEntry{"name", "some data."}})); + EXPECT_FALSE(saw_end_); + EXPECT_EQ(HpackDecodingError::kValueTooLong, decoder_.error()); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], Eq("Value length exceeds buffer limit")); +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoding_error.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoding_error.cc new file mode 100644 index 00000000000..9adb2e29b9b --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoding_error.cc @@ -0,0 +1,51 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/http2/hpack/decoder/hpack_decoding_error.h" + +namespace http2 { + +// static +absl::string_view HpackDecodingErrorToString(HpackDecodingError error) { + switch (error) { + case HpackDecodingError::kOk: + return "No error detected"; + case HpackDecodingError::kIndexVarintError: + return "Index varint beyond implementation limit"; + case HpackDecodingError::kNameLengthVarintError: + return "Name length varint beyond implementation limit"; + case HpackDecodingError::kValueLengthVarintError: + return "Value length varint beyond implementation limit"; + case HpackDecodingError::kNameTooLong: + return "Name length exceeds buffer limit"; + case HpackDecodingError::kValueTooLong: + return "Value length exceeds buffer limit"; + case HpackDecodingError::kNameHuffmanError: + return "Name Huffman encoding error"; + case HpackDecodingError::kValueHuffmanError: + return "Value Huffman encoding error"; + case HpackDecodingError::kMissingDynamicTableSizeUpdate: + return "Missing dynamic table size update"; + case HpackDecodingError::kInvalidIndex: + return "Invalid index in indexed header field representation"; + case HpackDecodingError::kInvalidNameIndex: + return "Invalid index in literal header field with indexed name " + "representation"; + case HpackDecodingError::kDynamicTableSizeUpdateNotAllowed: + return "Dynamic table size update not allowed"; + case HpackDecodingError::kInitialDynamicTableSizeUpdateIsAboveLowWaterMark: + return "Initial dynamic table size update is above low water mark"; + case HpackDecodingError::kDynamicTableSizeUpdateIsAboveAcknowledgedSetting: + return "Dynamic table size update is above acknowledged setting"; + case HpackDecodingError::kTruncatedBlock: + return "Block ends in the middle of an instruction"; + case HpackDecodingError::kFragmentTooLong: + return "Incoming data fragment exceeds buffer limit"; + case HpackDecodingError::kCompressedHeaderSizeExceedsLimit: + return "Total compressed HPACK data size exceeds limit"; + } + return "invalid HpackDecodingError value"; +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoding_error.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoding_error.h new file mode 100644 index 00000000000..e24eaf151c4 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoding_error.h @@ -0,0 +1,51 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODING_ERROR_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODING_ERROR_H_ + +#include "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_export.h" + +namespace http2 { + +enum class HpackDecodingError { + // No error detected so far. + kOk, + // Varint beyond implementation limit. + kIndexVarintError, + kNameLengthVarintError, + kValueLengthVarintError, + // String literal length exceeds buffer limit. + kNameTooLong, + kValueTooLong, + // Error in Huffman encoding. + kNameHuffmanError, + kValueHuffmanError, + // Next instruction should have been a dynamic table size update. + kMissingDynamicTableSizeUpdate, + // Invalid index in indexed header field representation. + kInvalidIndex, + // Invalid index in literal header field with indexed name representation. + kInvalidNameIndex, + // Dynamic table size update not allowed. + kDynamicTableSizeUpdateNotAllowed, + // Initial dynamic table size update is above low water mark. + kInitialDynamicTableSizeUpdateIsAboveLowWaterMark, + // Dynamic table size update is above acknowledged setting. + kDynamicTableSizeUpdateIsAboveAcknowledgedSetting, + // HPACK block ends in the middle of an instruction. + kTruncatedBlock, + // Incoming data fragment exceeds buffer limit. + kFragmentTooLong, + // Total compressed HPACK data size exceeds limit. + kCompressedHeaderSizeExceedsLimit, +}; + +QUICHE_EXPORT_PRIVATE absl::string_view HpackDecodingErrorToString( + HpackDecodingError error); + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODING_ERROR_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_collector.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_collector.cc new file mode 100644 index 00000000000..d2e7e2eccd4 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_collector.cc @@ -0,0 +1,301 @@ +// 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/hpack/decoder/hpack_entry_collector.h" + +#include "absl/strings/str_cat.h" +#include "quiche/http2/hpack/decoder/hpack_string_collector.h" +#include "quiche/http2/hpack/http2_hpack_constants.h" +#include "quiche/http2/platform/api/http2_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/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_collector.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_collector.h new file mode 100644 index 00000000000..aa3919c8fca --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_collector.h @@ -0,0 +1,159 @@ +// 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_HPACK_DECODER_HPACK_ENTRY_COLLECTOR_H_ +#define QUICHE_HTTP2_HPACK_DECODER_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/decoder/hpack_string_collector.h" +#include "quiche/http2/hpack/http2_hpack_constants.h" +#include "quiche/http2/hpack/tools/hpack_block_builder.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_HPACK_DECODER_HPACK_ENTRY_COLLECTOR_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_decoder.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_decoder.cc new file mode 100644 index 00000000000..c81e2984be5 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_decoder.cc @@ -0,0 +1,295 @@ +// 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/hpack/decoder/hpack_entry_decoder.h" + +#include <stddef.h> + +#include <cstdint> + +#include "absl/base/macros.h" +#include "quiche/http2/platform/api/http2_bug_tracker.h" +#include "quiche/http2/platform/api/http2_flag_utils.h" +#include "quiche/http2/platform/api/http2_flags.h" +#include "quiche/http2/platform/api/http2_logging.h" + +namespace http2 { +namespace { +// Converts calls from HpackStringDecoder when decoding a header name into the +// appropriate HpackEntryDecoderListener::OnName* calls. +class NameDecoderListener { + public: + explicit NameDecoderListener(HpackEntryDecoderListener* listener) + : listener_(listener) {} + bool OnStringStart(bool huffman_encoded, size_t len) { + listener_->OnNameStart(huffman_encoded, len); + return true; + } + void OnStringData(const char* data, size_t len) { + listener_->OnNameData(data, len); + } + void OnStringEnd() { listener_->OnNameEnd(); } + + private: + HpackEntryDecoderListener* listener_; +}; + +// Converts calls from HpackStringDecoder when decoding a header value into +// the appropriate HpackEntryDecoderListener::OnValue* calls. +class ValueDecoderListener { + public: + explicit ValueDecoderListener(HpackEntryDecoderListener* listener) + : listener_(listener) {} + bool OnStringStart(bool huffman_encoded, size_t len) { + listener_->OnValueStart(huffman_encoded, len); + return true; + } + void OnStringData(const char* data, size_t len) { + listener_->OnValueData(data, len); + } + void OnStringEnd() { listener_->OnValueEnd(); } + + private: + HpackEntryDecoderListener* listener_; +}; +} // namespace + +DecodeStatus HpackEntryDecoder::Start(DecodeBuffer* db, + HpackEntryDecoderListener* listener) { + QUICHE_DCHECK(db != nullptr); + QUICHE_DCHECK(listener != nullptr); + QUICHE_DCHECK(db->HasData()); + DecodeStatus status = entry_type_decoder_.Start(db); + switch (status) { + case DecodeStatus::kDecodeDone: + // The type of the entry and its varint fit into the current decode + // buffer. + if (entry_type_decoder_.entry_type() == HpackEntryType::kIndexedHeader) { + // The entry consists solely of the entry type and varint. + // This is by far the most common case in practice. + listener->OnIndexedHeader(entry_type_decoder_.varint()); + return DecodeStatus::kDecodeDone; + } + state_ = EntryDecoderState::kDecodedType; + return Resume(db, listener); + case DecodeStatus::kDecodeInProgress: + // Hit the end of the decode buffer before fully decoding + // the entry type and varint. + QUICHE_DCHECK_EQ(0u, db->Remaining()); + state_ = EntryDecoderState::kResumeDecodingType; + return status; + case DecodeStatus::kDecodeError: + HTTP2_CODE_COUNT_N(decompress_failure_3, 11, 23); + error_ = HpackDecodingError::kIndexVarintError; + // The varint must have been invalid (too long). + return status; + } + + HTTP2_BUG(http2_bug_63_1) << "Unreachable"; + return DecodeStatus::kDecodeError; +} + +DecodeStatus HpackEntryDecoder::Resume(DecodeBuffer* db, + HpackEntryDecoderListener* listener) { + QUICHE_DCHECK(db != nullptr); + QUICHE_DCHECK(listener != nullptr); + + DecodeStatus status; + + do { + switch (state_) { + case EntryDecoderState::kResumeDecodingType: + // entry_type_decoder_ returned kDecodeInProgress when last called. + HTTP2_DVLOG(1) << "kResumeDecodingType: db->Remaining=" + << db->Remaining(); + status = entry_type_decoder_.Resume(db); + if (status == DecodeStatus::kDecodeError) { + HTTP2_CODE_COUNT_N(decompress_failure_3, 12, 23); + error_ = HpackDecodingError::kIndexVarintError; + } + if (status != DecodeStatus::kDecodeDone) { + return status; + } + state_ = EntryDecoderState::kDecodedType; + ABSL_FALLTHROUGH_INTENDED; + + case EntryDecoderState::kDecodedType: + // entry_type_decoder_ returned kDecodeDone, now need to decide how + // to proceed. + HTTP2_DVLOG(1) << "kDecodedType: db->Remaining=" << db->Remaining(); + if (DispatchOnType(listener)) { + // All done. + return DecodeStatus::kDecodeDone; + } + continue; + + case EntryDecoderState::kStartDecodingName: + HTTP2_DVLOG(1) << "kStartDecodingName: db->Remaining=" + << db->Remaining(); + { + NameDecoderListener ncb(listener); + status = string_decoder_.Start(db, &ncb); + } + if (status != DecodeStatus::kDecodeDone) { + // On the assumption that the status is kDecodeInProgress, set + // state_ accordingly; unnecessary if status is kDecodeError, but + // that will only happen if the varint encoding the name's length + // is too long. + state_ = EntryDecoderState::kResumeDecodingName; + if (status == DecodeStatus::kDecodeError) { + HTTP2_CODE_COUNT_N(decompress_failure_3, 13, 23); + error_ = HpackDecodingError::kNameLengthVarintError; + } + return status; + } + state_ = EntryDecoderState::kStartDecodingValue; + ABSL_FALLTHROUGH_INTENDED; + + case EntryDecoderState::kStartDecodingValue: + HTTP2_DVLOG(1) << "kStartDecodingValue: db->Remaining=" + << db->Remaining(); + { + ValueDecoderListener vcb(listener); + status = string_decoder_.Start(db, &vcb); + } + if (status == DecodeStatus::kDecodeError) { + HTTP2_CODE_COUNT_N(decompress_failure_3, 14, 23); + error_ = HpackDecodingError::kValueLengthVarintError; + } + if (status == DecodeStatus::kDecodeDone) { + // Done with decoding the literal value, so we've reached the + // end of the header entry. + return status; + } + // On the assumption that the status is kDecodeInProgress, set + // state_ accordingly; unnecessary if status is kDecodeError, but + // that will only happen if the varint encoding the value's length + // is too long. + state_ = EntryDecoderState::kResumeDecodingValue; + return status; + + case EntryDecoderState::kResumeDecodingName: + // The literal name was split across decode buffers. + HTTP2_DVLOG(1) << "kResumeDecodingName: db->Remaining=" + << db->Remaining(); + { + NameDecoderListener ncb(listener); + status = string_decoder_.Resume(db, &ncb); + } + if (status != DecodeStatus::kDecodeDone) { + // On the assumption that the status is kDecodeInProgress, set + // state_ accordingly; unnecessary if status is kDecodeError, but + // that will only happen if the varint encoding the name's length + // is too long. + state_ = EntryDecoderState::kResumeDecodingName; + if (status == DecodeStatus::kDecodeError) { + HTTP2_CODE_COUNT_N(decompress_failure_3, 15, 23); + error_ = HpackDecodingError::kNameLengthVarintError; + } + return status; + } + state_ = EntryDecoderState::kStartDecodingValue; + break; + + case EntryDecoderState::kResumeDecodingValue: + // The literal value was split across decode buffers. + HTTP2_DVLOG(1) << "kResumeDecodingValue: db->Remaining=" + << db->Remaining(); + { + ValueDecoderListener vcb(listener); + status = string_decoder_.Resume(db, &vcb); + } + if (status == DecodeStatus::kDecodeError) { + HTTP2_CODE_COUNT_N(decompress_failure_3, 16, 23); + error_ = HpackDecodingError::kValueLengthVarintError; + } + if (status == DecodeStatus::kDecodeDone) { + // Done with decoding the value, therefore the entry as a whole. + return status; + } + // On the assumption that the status is kDecodeInProgress, set + // state_ accordingly; unnecessary if status is kDecodeError, but + // that will only happen if the varint encoding the value's length + // is too long. + state_ = EntryDecoderState::kResumeDecodingValue; + return status; + } + } while (true); +} + +bool HpackEntryDecoder::DispatchOnType(HpackEntryDecoderListener* listener) { + const HpackEntryType entry_type = entry_type_decoder_.entry_type(); + const uint32_t varint = static_cast<uint32_t>(entry_type_decoder_.varint()); + switch (entry_type) { + case HpackEntryType::kIndexedHeader: + // The entry consists solely of the entry type and varint. See: + // http://httpwg.org/specs/rfc7541.html#indexed.header.representation + listener->OnIndexedHeader(varint); + return true; + + case HpackEntryType::kIndexedLiteralHeader: + case HpackEntryType::kUnindexedLiteralHeader: + case HpackEntryType::kNeverIndexedLiteralHeader: + // The entry has a literal value, and if the varint is zero also has a + // literal name preceding the value. See: + // http://httpwg.org/specs/rfc7541.html#literal.header.representation + listener->OnStartLiteralHeader(entry_type, varint); + if (varint == 0) { + state_ = EntryDecoderState::kStartDecodingName; + } else { + state_ = EntryDecoderState::kStartDecodingValue; + } + return false; + + case HpackEntryType::kDynamicTableSizeUpdate: + // The entry consists solely of the entry type and varint. FWIW, I've + // never seen this type of entry in production (primarily browser + // traffic) so if you're designing an HPACK successor someday, consider + // dropping it or giving it a much longer prefix. See: + // http://httpwg.org/specs/rfc7541.html#encoding.context.update + listener->OnDynamicTableSizeUpdate(varint); + return true; + } + + HTTP2_BUG(http2_bug_63_2) << "Unreachable, entry_type=" << entry_type; + return true; +} + +void HpackEntryDecoder::OutputDebugString(std::ostream& out) const { + out << "HpackEntryDecoder(state=" << state_ << ", " << entry_type_decoder_ + << ", " << string_decoder_ << ")"; +} + +std::string HpackEntryDecoder::DebugString() const { + std::stringstream s; + s << *this; + return s.str(); +} + +std::ostream& operator<<(std::ostream& out, const HpackEntryDecoder& v) { + v.OutputDebugString(out); + return out; +} + +std::ostream& operator<<(std::ostream& out, + HpackEntryDecoder::EntryDecoderState state) { + typedef HpackEntryDecoder::EntryDecoderState EntryDecoderState; + switch (state) { + case EntryDecoderState::kResumeDecodingType: + return out << "kResumeDecodingType"; + case EntryDecoderState::kDecodedType: + return out << "kDecodedType"; + case EntryDecoderState::kStartDecodingName: + return out << "kStartDecodingName"; + case EntryDecoderState::kResumeDecodingName: + return out << "kResumeDecodingName"; + case EntryDecoderState::kStartDecodingValue: + return out << "kStartDecodingValue"; + case EntryDecoderState::kResumeDecodingValue: + return out << "kResumeDecodingValue"; + } + return out << static_cast<int>(state); +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_decoder.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_decoder.h new file mode 100644 index 00000000000..50ddd98039c --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_decoder.h @@ -0,0 +1,91 @@ +// 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_HPACK_DECODER_HPACK_ENTRY_DECODER_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_H_ + +// HpackEntryDecoder decodes a single HPACK entry (i.e. one header or one +// dynamic table size update), in a resumable fashion. The first call, Start(), +// must provide a non-empty decode buffer. Continue with calls to Resume() if +// Start, and any subsequent calls to Resume, returns kDecodeInProgress. + +#include <string> + +#include "quiche/http2/decoder/decode_buffer.h" +#include "quiche/http2/decoder/decode_status.h" +#include "quiche/http2/hpack/decoder/hpack_decoding_error.h" +#include "quiche/http2/hpack/decoder/hpack_entry_decoder_listener.h" +#include "quiche/http2/hpack/decoder/hpack_entry_type_decoder.h" +#include "quiche/http2/hpack/decoder/hpack_string_decoder.h" +#include "quiche/http2/hpack/http2_hpack_constants.h" +#include "quiche/http2/platform/api/http2_logging.h" +#include "quiche/common/platform/api/quiche_export.h" + +namespace http2 { + +class QUICHE_EXPORT_PRIVATE HpackEntryDecoder { + public: + enum class EntryDecoderState { + // Have started decoding the type/varint, but didn't finish on the previous + // attempt. Next state is kResumeDecodingType or kDecodedType. + kResumeDecodingType, + + // Have just finished decoding the type/varint. Final state if the type is + // kIndexedHeader or kDynamicTableSizeUpdate. Otherwise, the next state is + // kStartDecodingName (if the varint is 0), else kStartDecodingValue. + kDecodedType, + + // Ready to start decoding the literal name of a header entry. Next state + // is kResumeDecodingName (if the name is split across decode buffers), + // else kStartDecodingValue. + kStartDecodingName, + + // Resume decoding the literal name of a header that is split across decode + // buffers. + kResumeDecodingName, + + // Ready to start decoding the literal value of a header entry. Final state + // if the value string is entirely in the decode buffer, else the next state + // is kResumeDecodingValue. + kStartDecodingValue, + + // Resume decoding the literal value of a header that is split across decode + // buffers. + kResumeDecodingValue, + }; + + // Only call when the decode buffer has data (i.e. HpackBlockDecoder must + // not call until there is data). + DecodeStatus Start(DecodeBuffer* db, HpackEntryDecoderListener* listener); + + // Only call Resume if the previous call (Start or Resume) returned + // kDecodeInProgress; Resume is also called from Start when it has succeeded + // in decoding the entry type and its varint. + DecodeStatus Resume(DecodeBuffer* db, HpackEntryDecoderListener* listener); + + // Return error code after decoding error occurred. + HpackDecodingError error() const { return error_; } + + std::string DebugString() const; + void OutputDebugString(std::ostream& out) const; + + private: + // Implements handling state kDecodedType. + bool DispatchOnType(HpackEntryDecoderListener* listener); + + HpackEntryTypeDecoder entry_type_decoder_; + HpackStringDecoder string_decoder_; + EntryDecoderState state_ = EntryDecoderState(); + HpackDecodingError error_ = HpackDecodingError::kOk; +}; + +QUICHE_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const HpackEntryDecoder& v); +QUICHE_EXPORT_PRIVATE std::ostream& operator<<( + std::ostream& out, + HpackEntryDecoder::EntryDecoderState state); + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_decoder_listener.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_decoder_listener.cc new file mode 100644 index 00000000000..c7fe4b42a64 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_decoder_listener.cc @@ -0,0 +1,81 @@ +// 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/hpack/decoder/hpack_entry_decoder_listener.h" + +#include "quiche/http2/platform/api/http2_logging.h" + +namespace http2 { + +void HpackEntryDecoderVLoggingListener::OnIndexedHeader(size_t index) { + HTTP2_VLOG(1) << "OnIndexedHeader, index=" << index; + if (wrapped_) { + wrapped_->OnIndexedHeader(index); + } +} + +void HpackEntryDecoderVLoggingListener::OnStartLiteralHeader( + HpackEntryType entry_type, + size_t maybe_name_index) { + HTTP2_VLOG(1) << "OnStartLiteralHeader: entry_type=" << entry_type + << ", maybe_name_index=" << maybe_name_index; + if (wrapped_) { + wrapped_->OnStartLiteralHeader(entry_type, maybe_name_index); + } +} + +void HpackEntryDecoderVLoggingListener::OnNameStart(bool huffman_encoded, + size_t len) { + HTTP2_VLOG(1) << "OnNameStart: H=" << huffman_encoded << ", len=" << len; + if (wrapped_) { + wrapped_->OnNameStart(huffman_encoded, len); + } +} + +void HpackEntryDecoderVLoggingListener::OnNameData(const char* data, + size_t len) { + HTTP2_VLOG(1) << "OnNameData: len=" << len; + if (wrapped_) { + wrapped_->OnNameData(data, len); + } +} + +void HpackEntryDecoderVLoggingListener::OnNameEnd() { + HTTP2_VLOG(1) << "OnNameEnd"; + if (wrapped_) { + wrapped_->OnNameEnd(); + } +} + +void HpackEntryDecoderVLoggingListener::OnValueStart(bool huffman_encoded, + size_t len) { + HTTP2_VLOG(1) << "OnValueStart: H=" << huffman_encoded << ", len=" << len; + if (wrapped_) { + wrapped_->OnValueStart(huffman_encoded, len); + } +} + +void HpackEntryDecoderVLoggingListener::OnValueData(const char* data, + size_t len) { + HTTP2_VLOG(1) << "OnValueData: len=" << len; + if (wrapped_) { + wrapped_->OnValueData(data, len); + } +} + +void HpackEntryDecoderVLoggingListener::OnValueEnd() { + HTTP2_VLOG(1) << "OnValueEnd"; + if (wrapped_) { + wrapped_->OnValueEnd(); + } +} + +void HpackEntryDecoderVLoggingListener::OnDynamicTableSizeUpdate(size_t size) { + HTTP2_VLOG(1) << "OnDynamicTableSizeUpdate: size=" << size; + if (wrapped_) { + wrapped_->OnDynamicTableSizeUpdate(size); + } +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_decoder_listener.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_decoder_listener.h new file mode 100644 index 00000000000..1ba93391d67 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_decoder_listener.h @@ -0,0 +1,110 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_LISTENER_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_LISTENER_H_ + +// Defines HpackEntryDecoderListener, the base class of listeners that +// HpackEntryDecoder calls. Also defines HpackEntryDecoderVLoggingListener +// which logs before calling another HpackEntryDecoderListener implementation. + +#include <stddef.h> + +#include "quiche/http2/hpack/http2_hpack_constants.h" +#include "quiche/common/platform/api/quiche_export.h" + +namespace http2 { + +class QUICHE_EXPORT_PRIVATE HpackEntryDecoderListener { + public: + virtual ~HpackEntryDecoderListener() {} + + // Called when an indexed header (i.e. one in the static or dynamic table) has + // been decoded from an HPACK block. index is supposed to be non-zero, but + // that has not been checked by the caller. + virtual void OnIndexedHeader(size_t index) = 0; + + // Called when the start of a header with a literal value, and maybe a literal + // name, has been decoded. maybe_name_index is zero if the header has a + // literal name, else it is a reference into the static or dynamic table, from + // which the name should be determined. When the name is literal, the next + // call will be to OnNameStart; else it will be to OnValueStart. entry_type + // indicates whether the peer has added the entry to its dynamic table, and + // whether a proxy is permitted to do so when forwarding the entry. + virtual void OnStartLiteralHeader(HpackEntryType entry_type, + size_t maybe_name_index) = 0; + + // Called when the encoding (Huffman compressed or plain text) and the encoded + // length of a literal name has been decoded. OnNameData will be called next, + // and repeatedly until the sum of lengths passed to OnNameData is len. + virtual void OnNameStart(bool huffman_encoded, size_t len) = 0; + + // Called when len bytes of an encoded header name have been decoded. + virtual void OnNameData(const char* data, size_t len) = 0; + + // Called after the entire name has been passed to OnNameData. + // OnValueStart will be called next. + virtual void OnNameEnd() = 0; + + // Called when the encoding (Huffman compressed or plain text) and the encoded + // length of a literal value has been decoded. OnValueData will be called + // next, and repeatedly until the sum of lengths passed to OnValueData is len. + virtual void OnValueStart(bool huffman_encoded, size_t len) = 0; + + // Called when len bytes of an encoded header value have been decoded. + virtual void OnValueData(const char* data, size_t len) = 0; + + // Called after the entire value has been passed to OnValueData, marking the + // end of a header entry with a literal value, and maybe a literal name. + virtual void OnValueEnd() = 0; + + // Called when an update to the size of the peer's dynamic table has been + // decoded. + virtual void OnDynamicTableSizeUpdate(size_t size) = 0; +}; + +class QUICHE_EXPORT_PRIVATE HpackEntryDecoderVLoggingListener + : public HpackEntryDecoderListener { + public: + HpackEntryDecoderVLoggingListener() : wrapped_(nullptr) {} + explicit HpackEntryDecoderVLoggingListener(HpackEntryDecoderListener* wrapped) + : wrapped_(wrapped) {} + ~HpackEntryDecoderVLoggingListener() override {} + + void OnIndexedHeader(size_t index) override; + void OnStartLiteralHeader(HpackEntryType entry_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; + + private: + HpackEntryDecoderListener* const wrapped_; +}; + +// A no-op implementation of HpackEntryDecoderListener. +class QUICHE_EXPORT_PRIVATE HpackEntryDecoderNoOpListener + : public HpackEntryDecoderListener { + public: + ~HpackEntryDecoderNoOpListener() override {} + + void OnIndexedHeader(size_t /*index*/) override {} + void OnStartLiteralHeader(HpackEntryType /*entry_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 {} +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_LISTENER_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_decoder_test.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_decoder_test.cc new file mode 100644 index 00000000000..275495d6ee3 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_decoder_test.cc @@ -0,0 +1,202 @@ +// 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/hpack/decoder/hpack_entry_decoder.h" + +// Tests of HpackEntryDecoder. + +#include <cstdint> + +#include "quiche/http2/hpack/decoder/hpack_entry_collector.h" +#include "quiche/http2/hpack/tools/hpack_block_builder.h" +#include "quiche/http2/test_tools/http2_random.h" +#include "quiche/http2/tools/random_decoder_test.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/common/platform/api/quiche_test_helpers.h" + +namespace http2 { +namespace test { +namespace { + +class HpackEntryDecoderTest : public RandomDecoderTest { + protected: + HpackEntryDecoderTest() : listener_(&collector_) {} + + DecodeStatus StartDecoding(DecodeBuffer* b) override { + collector_.Clear(); + return decoder_.Start(b, &listener_); + } + + DecodeStatus ResumeDecoding(DecodeBuffer* b) override { + return decoder_.Resume(b, &listener_); + } + + AssertionResult DecodeAndValidateSeveralWays(DecodeBuffer* db, + const Validator& validator) { + // StartDecoding, above, requires the DecodeBuffer be non-empty so that it + // can call Start with the prefix byte. + bool return_non_zero_on_first = true; + return RandomDecoderTest::DecodeAndValidateSeveralWays( + db, return_non_zero_on_first, validator); + } + + AssertionResult DecodeAndValidateSeveralWays(const HpackBlockBuilder& hbb, + const Validator& validator) { + DecodeBuffer db(hbb.buffer()); + return DecodeAndValidateSeveralWays(&db, validator); + } + + HpackEntryDecoder decoder_; + HpackEntryCollector collector_; + HpackEntryDecoderVLoggingListener listener_; +}; + +TEST_F(HpackEntryDecoderTest, IndexedHeader_Literals) { + { + const char input[] = {'\x82'}; // == Index 2 == + DecodeBuffer b(input); + auto do_check = [this]() { return collector_.ValidateIndexedHeader(2); }; + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); + } + collector_.Clear(); + { + const char input[] = {'\xfe'}; // == Index 126 == + DecodeBuffer b(input); + auto do_check = [this]() { return collector_.ValidateIndexedHeader(126); }; + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); + } + collector_.Clear(); + { + const char input[] = {'\xff', '\x00'}; // == Index 127 == + DecodeBuffer b(input); + auto do_check = [this]() { return collector_.ValidateIndexedHeader(127); }; + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); + } +} + +TEST_F(HpackEntryDecoderTest, IndexedHeader_Various) { + // Indices chosen to hit encoding and table boundaries. + for (const uint32_t ndx : {1, 2, 61, 62, 63, 126, 127, 254, 255, 256}) { + HpackBlockBuilder hbb; + hbb.AppendIndexedHeader(ndx); + + auto do_check = [this, ndx]() { + return collector_.ValidateIndexedHeader(ndx); + }; + EXPECT_TRUE( + DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); + } +} + +TEST_F(HpackEntryDecoderTest, IndexedLiteralValue_Literal) { + const char input[] = + "\x7f" // == Literal indexed, name index 0x40 == + "\x01" // 2nd byte of name index (0x01 + 0x3f == 0x40) + "\x0d" // Value length (13) + "custom-header"; // Value + DecodeBuffer b(input, sizeof input - 1); + auto do_check = [this]() { + return collector_.ValidateLiteralValueHeader( + HpackEntryType::kIndexedLiteralHeader, 0x40, false, "custom-header"); + }; + EXPECT_TRUE(DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); +} + +TEST_F(HpackEntryDecoderTest, IndexedLiteralNameValue_Literal) { + const char input[] = + "\x40" // == Literal indexed == + "\x0a" // Name length (10) + "custom-key" // Name + "\x0d" // Value length (13) + "custom-header"; // Value + + DecodeBuffer b(input, sizeof input - 1); + auto do_check = [this]() { + return collector_.ValidateLiteralNameValueHeader( + HpackEntryType::kIndexedLiteralHeader, false, "custom-key", false, + "custom-header"); + }; + EXPECT_TRUE(DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); +} + +TEST_F(HpackEntryDecoderTest, DynamicTableSizeUpdate_Literal) { + // Size update, length 31. + const char input[] = "\x3f\x00"; + DecodeBuffer b(input, 2); + auto do_check = [this]() { + return collector_.ValidateDynamicTableSizeUpdate(31); + }; + EXPECT_TRUE(DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); +} + +class HpackLiteralEntryDecoderTest + : public HpackEntryDecoderTest, + public ::testing::WithParamInterface<HpackEntryType> { + protected: + HpackLiteralEntryDecoderTest() : entry_type_(GetParam()) {} + + const HpackEntryType entry_type_; +}; + +INSTANTIATE_TEST_SUITE_P( + AllLiteralTypes, HpackLiteralEntryDecoderTest, + testing::Values(HpackEntryType::kIndexedLiteralHeader, + HpackEntryType::kUnindexedLiteralHeader, + HpackEntryType::kNeverIndexedLiteralHeader)); + +TEST_P(HpackLiteralEntryDecoderTest, RandNameIndexAndLiteralValue) { + for (int n = 0; n < 10; n++) { + const uint32_t ndx = 1 + Random().Rand8(); + const bool value_is_huffman_encoded = (n % 2) == 0; + const std::string value = Random().RandString(Random().Rand8()); + HpackBlockBuilder hbb; + hbb.AppendNameIndexAndLiteralValue(entry_type_, ndx, + value_is_huffman_encoded, value); + auto do_check = [this, ndx, value_is_huffman_encoded, + value]() -> AssertionResult { + return collector_.ValidateLiteralValueHeader( + entry_type_, ndx, value_is_huffman_encoded, value); + }; + EXPECT_TRUE( + DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); + } +} + +TEST_P(HpackLiteralEntryDecoderTest, RandLiteralNameAndValue) { + for (int n = 0; n < 10; n++) { + const bool name_is_huffman_encoded = (n & 1) == 0; + const int name_len = 1 + Random().Rand8(); + const std::string name = Random().RandString(name_len); + const bool value_is_huffman_encoded = (n & 2) == 0; + const int value_len = Random().Skewed(10); + const std::string value = Random().RandString(value_len); + HpackBlockBuilder hbb; + hbb.AppendLiteralNameAndValue(entry_type_, name_is_huffman_encoded, name, + value_is_huffman_encoded, value); + auto do_check = [this, name_is_huffman_encoded, name, + value_is_huffman_encoded, value]() -> AssertionResult { + return collector_.ValidateLiteralNameValueHeader( + entry_type_, name_is_huffman_encoded, name, value_is_huffman_encoded, + value); + }; + EXPECT_TRUE( + DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); + } +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_type_decoder.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_type_decoder.cc new file mode 100644 index 00000000000..2bf6d8dd36c --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_type_decoder.cc @@ -0,0 +1,362 @@ +// 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/hpack/decoder/hpack_entry_type_decoder.h" + +#include "absl/strings/str_cat.h" +#include "quiche/http2/platform/api/http2_bug_tracker.h" +#include "quiche/http2/platform/api/http2_flag_utils.h" +#include "quiche/http2/platform/api/http2_flags.h" +#include "quiche/http2/platform/api/http2_logging.h" + +namespace http2 { + +std::string HpackEntryTypeDecoder::DebugString() const { + return absl::StrCat( + "HpackEntryTypeDecoder(varint_decoder=", varint_decoder_.DebugString(), + ", entry_type=", entry_type_, ")"); +} + +std::ostream& operator<<(std::ostream& out, const HpackEntryTypeDecoder& v) { + return out << v.DebugString(); +} + +// This ridiculous looking function turned out to be the winner in benchmarking +// of several very different alternative implementations. It would be even +// faster (~7%) if inlined in the header file, but I'm not sure if that is +// worth doing... yet. +// TODO(jamessynge): Benchmark again at a higher level (e.g. at least at the +// full HTTP/2 decoder level, but preferably still higher) to determine if the +// alternatives that take less code/data space are preferable in that situation. +DecodeStatus HpackEntryTypeDecoder::Start(DecodeBuffer* db) { + QUICHE_DCHECK(db != nullptr); + QUICHE_DCHECK(db->HasData()); + + // The high four bits (nibble) of first byte of the entry determine the type + // of the entry, and may also be the initial bits of the varint that + // represents an index or table size. Note the use of the word 'initial' + // rather than 'high'; the HPACK encoding of varints is not in network + // order (i.e. not big-endian, the high-order byte isn't first), nor in + // little-endian order. See: + // http://httpwg.org/specs/rfc7541.html#integer.representation + uint8_t byte = db->DecodeUInt8(); + switch (byte) { + case 0b00000000: + case 0b00000001: + case 0b00000010: + case 0b00000011: + case 0b00000100: + case 0b00000101: + case 0b00000110: + case 0b00000111: + case 0b00001000: + case 0b00001001: + case 0b00001010: + case 0b00001011: + case 0b00001100: + case 0b00001101: + case 0b00001110: + // The low 4 bits of |byte| are the initial bits of the varint. + // One of those bits is 0, so the varint is only one byte long. + entry_type_ = HpackEntryType::kUnindexedLiteralHeader; + varint_decoder_.set_value(byte); + return DecodeStatus::kDecodeDone; + + case 0b00001111: + // The low 4 bits of |byte| are the initial bits of the varint. All 4 + // are 1, so the varint extends into another byte. + entry_type_ = HpackEntryType::kUnindexedLiteralHeader; + return varint_decoder_.StartExtended(4, db); + + case 0b00010000: + case 0b00010001: + case 0b00010010: + case 0b00010011: + case 0b00010100: + case 0b00010101: + case 0b00010110: + case 0b00010111: + case 0b00011000: + case 0b00011001: + case 0b00011010: + case 0b00011011: + case 0b00011100: + case 0b00011101: + case 0b00011110: + // The low 4 bits of |byte| are the initial bits of the varint. + // One of those bits is 0, so the varint is only one byte long. + entry_type_ = HpackEntryType::kNeverIndexedLiteralHeader; + varint_decoder_.set_value(byte & 0x0f); + return DecodeStatus::kDecodeDone; + + case 0b00011111: + // The low 4 bits of |byte| are the initial bits of the varint. + // All of those bits are 1, so the varint extends into another byte. + entry_type_ = HpackEntryType::kNeverIndexedLiteralHeader; + return varint_decoder_.StartExtended(4, db); + + case 0b00100000: + case 0b00100001: + case 0b00100010: + case 0b00100011: + case 0b00100100: + case 0b00100101: + case 0b00100110: + case 0b00100111: + case 0b00101000: + case 0b00101001: + case 0b00101010: + case 0b00101011: + case 0b00101100: + case 0b00101101: + case 0b00101110: + case 0b00101111: + case 0b00110000: + case 0b00110001: + case 0b00110010: + case 0b00110011: + case 0b00110100: + case 0b00110101: + case 0b00110110: + case 0b00110111: + case 0b00111000: + case 0b00111001: + case 0b00111010: + case 0b00111011: + case 0b00111100: + case 0b00111101: + case 0b00111110: + entry_type_ = HpackEntryType::kDynamicTableSizeUpdate; + // The low 5 bits of |byte| are the initial bits of the varint. + // One of those bits is 0, so the varint is only one byte long. + varint_decoder_.set_value(byte & 0x01f); + return DecodeStatus::kDecodeDone; + + case 0b00111111: + entry_type_ = HpackEntryType::kDynamicTableSizeUpdate; + // The low 5 bits of |byte| are the initial bits of the varint. + // All of those bits are 1, so the varint extends into another byte. + return varint_decoder_.StartExtended(5, db); + + case 0b01000000: + case 0b01000001: + case 0b01000010: + case 0b01000011: + case 0b01000100: + case 0b01000101: + case 0b01000110: + case 0b01000111: + case 0b01001000: + case 0b01001001: + case 0b01001010: + case 0b01001011: + case 0b01001100: + case 0b01001101: + case 0b01001110: + case 0b01001111: + case 0b01010000: + case 0b01010001: + case 0b01010010: + case 0b01010011: + case 0b01010100: + case 0b01010101: + case 0b01010110: + case 0b01010111: + case 0b01011000: + case 0b01011001: + case 0b01011010: + case 0b01011011: + case 0b01011100: + case 0b01011101: + case 0b01011110: + case 0b01011111: + case 0b01100000: + case 0b01100001: + case 0b01100010: + case 0b01100011: + case 0b01100100: + case 0b01100101: + case 0b01100110: + case 0b01100111: + case 0b01101000: + case 0b01101001: + case 0b01101010: + case 0b01101011: + case 0b01101100: + case 0b01101101: + case 0b01101110: + case 0b01101111: + case 0b01110000: + case 0b01110001: + case 0b01110010: + case 0b01110011: + case 0b01110100: + case 0b01110101: + case 0b01110110: + case 0b01110111: + case 0b01111000: + case 0b01111001: + case 0b01111010: + case 0b01111011: + case 0b01111100: + case 0b01111101: + case 0b01111110: + entry_type_ = HpackEntryType::kIndexedLiteralHeader; + // The low 6 bits of |byte| are the initial bits of the varint. + // One of those bits is 0, so the varint is only one byte long. + varint_decoder_.set_value(byte & 0x03f); + return DecodeStatus::kDecodeDone; + + case 0b01111111: + entry_type_ = HpackEntryType::kIndexedLiteralHeader; + // The low 6 bits of |byte| are the initial bits of the varint. + // All of those bits are 1, so the varint extends into another byte. + return varint_decoder_.StartExtended(6, db); + + case 0b10000000: + case 0b10000001: + case 0b10000010: + case 0b10000011: + case 0b10000100: + case 0b10000101: + case 0b10000110: + case 0b10000111: + case 0b10001000: + case 0b10001001: + case 0b10001010: + case 0b10001011: + case 0b10001100: + case 0b10001101: + case 0b10001110: + case 0b10001111: + case 0b10010000: + case 0b10010001: + case 0b10010010: + case 0b10010011: + case 0b10010100: + case 0b10010101: + case 0b10010110: + case 0b10010111: + case 0b10011000: + case 0b10011001: + case 0b10011010: + case 0b10011011: + case 0b10011100: + case 0b10011101: + case 0b10011110: + case 0b10011111: + case 0b10100000: + case 0b10100001: + case 0b10100010: + case 0b10100011: + case 0b10100100: + case 0b10100101: + case 0b10100110: + case 0b10100111: + case 0b10101000: + case 0b10101001: + case 0b10101010: + case 0b10101011: + case 0b10101100: + case 0b10101101: + case 0b10101110: + case 0b10101111: + case 0b10110000: + case 0b10110001: + case 0b10110010: + case 0b10110011: + case 0b10110100: + case 0b10110101: + case 0b10110110: + case 0b10110111: + case 0b10111000: + case 0b10111001: + case 0b10111010: + case 0b10111011: + case 0b10111100: + case 0b10111101: + case 0b10111110: + case 0b10111111: + case 0b11000000: + case 0b11000001: + case 0b11000010: + case 0b11000011: + case 0b11000100: + case 0b11000101: + case 0b11000110: + case 0b11000111: + case 0b11001000: + case 0b11001001: + case 0b11001010: + case 0b11001011: + case 0b11001100: + case 0b11001101: + case 0b11001110: + case 0b11001111: + case 0b11010000: + case 0b11010001: + case 0b11010010: + case 0b11010011: + case 0b11010100: + case 0b11010101: + case 0b11010110: + case 0b11010111: + case 0b11011000: + case 0b11011001: + case 0b11011010: + case 0b11011011: + case 0b11011100: + case 0b11011101: + case 0b11011110: + case 0b11011111: + case 0b11100000: + case 0b11100001: + case 0b11100010: + case 0b11100011: + case 0b11100100: + case 0b11100101: + case 0b11100110: + case 0b11100111: + case 0b11101000: + case 0b11101001: + case 0b11101010: + case 0b11101011: + case 0b11101100: + case 0b11101101: + case 0b11101110: + case 0b11101111: + case 0b11110000: + case 0b11110001: + case 0b11110010: + case 0b11110011: + case 0b11110100: + case 0b11110101: + case 0b11110110: + case 0b11110111: + case 0b11111000: + case 0b11111001: + case 0b11111010: + case 0b11111011: + case 0b11111100: + case 0b11111101: + case 0b11111110: + entry_type_ = HpackEntryType::kIndexedHeader; + // The low 7 bits of |byte| are the initial bits of the varint. + // One of those bits is 0, so the varint is only one byte long. + varint_decoder_.set_value(byte & 0x07f); + return DecodeStatus::kDecodeDone; + + case 0b11111111: + entry_type_ = HpackEntryType::kIndexedHeader; + // The low 7 bits of |byte| are the initial bits of the varint. + // All of those bits are 1, so the varint extends into another byte. + return varint_decoder_.StartExtended(7, db); + } + HTTP2_BUG(http2_bug_66_1) + << "Unreachable, byte=" << std::hex << static_cast<uint32_t>(byte); + HTTP2_CODE_COUNT_N(decompress_failure_3, 17, 23); + return DecodeStatus::kDecodeError; +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_type_decoder.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_type_decoder.h new file mode 100644 index 00000000000..94554d4a4ba --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_type_decoder.h @@ -0,0 +1,57 @@ +// 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_HPACK_DECODER_HPACK_ENTRY_TYPE_DECODER_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_TYPE_DECODER_H_ + +// Decodes the type of an HPACK entry, and the variable length integer whose +// prefix is in the low-order bits of the same byte, "below" the type bits. +// The integer represents an index into static or dynamic table, which may be +// zero, or is the new size limit of the dynamic table. + +#include <cstdint> +#include <string> + +#include "quiche/http2/decoder/decode_buffer.h" +#include "quiche/http2/decoder/decode_status.h" +#include "quiche/http2/hpack/http2_hpack_constants.h" +#include "quiche/http2/hpack/varint/hpack_varint_decoder.h" +#include "quiche/http2/platform/api/http2_logging.h" +#include "quiche/common/platform/api/quiche_export.h" + +namespace http2 { + +class QUICHE_EXPORT_PRIVATE HpackEntryTypeDecoder { + public: + // Only call when the decode buffer has data (i.e. HpackEntryDecoder must + // not call until there is data). + DecodeStatus Start(DecodeBuffer* db); + + // Only call Resume if the previous call (Start or Resume) returned + // DecodeStatus::kDecodeInProgress. + DecodeStatus Resume(DecodeBuffer* db) { return varint_decoder_.Resume(db); } + + // Returns the decoded entry type. Only call if the preceding call to Start + // or Resume returned kDecodeDone. + HpackEntryType entry_type() const { return entry_type_; } + + // Returns the decoded variable length integer. Only call if the + // preceding call to Start or Resume returned kDecodeDone. + uint64_t varint() const { return varint_decoder_.value(); } + + std::string DebugString() const; + + private: + HpackVarintDecoder varint_decoder_; + + // This field is initialized just to keep ASAN happy about reading it + // from DebugString(). + HpackEntryType entry_type_ = HpackEntryType::kIndexedHeader; +}; + +QUICHE_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const HpackEntryTypeDecoder& v); + +} // namespace http2 +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_TYPE_DECODER_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_type_decoder_test.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_type_decoder_test.cc new file mode 100644 index 00000000000..be0d5acfad6 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_type_decoder_test.cc @@ -0,0 +1,85 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/http2/hpack/decoder/hpack_entry_type_decoder.h" + +#include <vector> + +#include "quiche/http2/hpack/tools/hpack_block_builder.h" +#include "quiche/http2/platform/api/http2_logging.h" +#include "quiche/http2/tools/random_decoder_test.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/common/platform/api/quiche_test_helpers.h" + +using ::testing::AssertionSuccess; + +namespace http2 { +namespace test { +namespace { +const bool kReturnNonZeroOnFirst = true; + +class HpackEntryTypeDecoderTest : public RandomDecoderTest { + protected: + DecodeStatus StartDecoding(DecodeBuffer* b) override { + QUICHE_CHECK_LT(0u, b->Remaining()); + return decoder_.Start(b); + } + + DecodeStatus ResumeDecoding(DecodeBuffer* b) override { + return decoder_.Resume(b); + } + + HpackEntryTypeDecoder decoder_; +}; + +TEST_F(HpackEntryTypeDecoderTest, DynamicTableSizeUpdate) { + for (uint32_t size = 0; size < 1000 * 1000; size += 256) { + HpackBlockBuilder bb; + bb.AppendDynamicTableSizeUpdate(size); + DecodeBuffer db(bb.buffer()); + auto validator = [size, this]() -> AssertionResult { + VERIFY_EQ(HpackEntryType::kDynamicTableSizeUpdate, decoder_.entry_type()); + VERIFY_EQ(size, decoder_.varint()); + return AssertionSuccess(); + }; + EXPECT_TRUE(DecodeAndValidateSeveralWays(&db, kReturnNonZeroOnFirst, + ValidateDoneAndEmpty(validator))) + << "\nentry_type=kDynamicTableSizeUpdate, size=" << size; + // Run the validator again to make sure that DecodeAndValidateSeveralWays + // did the right thing. + EXPECT_TRUE(validator()); + } +} + +TEST_F(HpackEntryTypeDecoderTest, HeaderWithIndex) { + std::vector<HpackEntryType> entry_types = { + HpackEntryType::kIndexedHeader, + HpackEntryType::kIndexedLiteralHeader, + HpackEntryType::kUnindexedLiteralHeader, + HpackEntryType::kNeverIndexedLiteralHeader, + }; + for (const HpackEntryType entry_type : entry_types) { + const uint32_t first = entry_type == HpackEntryType::kIndexedHeader ? 1 : 0; + for (uint32_t index = first; index < 1000; ++index) { + HpackBlockBuilder bb; + bb.AppendEntryTypeAndVarint(entry_type, index); + DecodeBuffer db(bb.buffer()); + auto validator = [entry_type, index, this]() -> AssertionResult { + VERIFY_EQ(entry_type, decoder_.entry_type()); + VERIFY_EQ(index, decoder_.varint()); + return AssertionSuccess(); + }; + EXPECT_TRUE(DecodeAndValidateSeveralWays(&db, kReturnNonZeroOnFirst, + ValidateDoneAndEmpty(validator))) + << "\nentry_type=" << entry_type << ", index=" << index; + // Run the validator again to make sure that DecodeAndValidateSeveralWays + // did the right thing. + EXPECT_TRUE(validator()); + } + } +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_collector.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_collector.cc new file mode 100644 index 00000000000..5709f2d6451 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_collector.cc @@ -0,0 +1,124 @@ +// 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/hpack/decoder/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/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_collector.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_collector.h new file mode 100644 index 00000000000..270c8a5906f --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_collector.h @@ -0,0 +1,63 @@ +// 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_HPACK_DECODER_HPACK_STRING_COLLECTOR_H_ +#define QUICHE_HTTP2_HPACK_DECODER_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_test.h" + +namespace http2 { +namespace test { + +// Records the callbacks associated with a decoding a string; must +// call Clear() between decoding successive strings. +struct 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); + +std::ostream& operator<<(std::ostream& out, const HpackStringCollector& v); + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_COLLECTOR_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_decoder.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_decoder.cc new file mode 100644 index 00000000000..f2a4bf80664 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_decoder.cc @@ -0,0 +1,35 @@ +// 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/hpack/decoder/hpack_string_decoder.h" + +#include "absl/strings/str_cat.h" + +namespace http2 { + +std::string HpackStringDecoder::DebugString() const { + return absl::StrCat("HpackStringDecoder(state=", StateToString(state_), + ", length=", length_decoder_.DebugString(), + ", remaining=", remaining_, + ", huffman=", huffman_encoded_ ? "true)" : "false)"); +} + +// static +std::string HpackStringDecoder::StateToString(StringDecoderState v) { + switch (v) { + case kStartDecodingLength: + return "kStartDecodingLength"; + case kDecodingString: + return "kDecodingString"; + case kResumeDecodingLength: + return "kResumeDecodingLength"; + } + return absl::StrCat("UNKNOWN_STATE(", static_cast<uint32_t>(v), ")"); +} + +std::ostream& operator<<(std::ostream& out, const HpackStringDecoder& v) { + return out << v.DebugString(); +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_decoder.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_decoder.h new file mode 100644 index 00000000000..a8566858aba --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_decoder.h @@ -0,0 +1,209 @@ +// 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_HPACK_DECODER_HPACK_STRING_DECODER_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_ + +// HpackStringDecoder decodes strings encoded per the HPACK spec; this does +// not mean decompressing Huffman encoded strings, just identifying the length, +// encoding and contents for a listener. + +#include <stddef.h> + +#include <algorithm> +#include <cstdint> +#include <string> + +#include "absl/base/macros.h" +#include "quiche/http2/decoder/decode_buffer.h" +#include "quiche/http2/decoder/decode_status.h" +#include "quiche/http2/hpack/varint/hpack_varint_decoder.h" +#include "quiche/http2/platform/api/http2_logging.h" +#include "quiche/common/platform/api/quiche_export.h" + +namespace http2 { + +// Decodes a single string in an HPACK header entry. The high order bit of +// the first byte of the length is the H (Huffman) bit indicating whether +// the value is Huffman encoded, and the remainder of the byte is the first +// 7 bits of an HPACK varint. +// +// Call Start() to begin decoding; if it returns kDecodeInProgress, then call +// Resume() when more input is available, repeating until kDecodeInProgress is +// not returned. If kDecodeDone or kDecodeError is returned, then Resume() must +// not be called until Start() has been called to start decoding a new string. +class QUICHE_EXPORT_PRIVATE HpackStringDecoder { + public: + enum StringDecoderState { + kStartDecodingLength, + kDecodingString, + kResumeDecodingLength, + }; + + template <class Listener> + DecodeStatus Start(DecodeBuffer* db, Listener* cb) { + // Fast decode path is used if the string is under 127 bytes and the + // entire length of the string is in the decode buffer. More than 83% of + // string lengths are encoded in just one byte. + if (db->HasData() && (*db->cursor() & 0x7f) != 0x7f) { + // The string is short. + uint8_t h_and_prefix = db->DecodeUInt8(); + uint8_t length = h_and_prefix & 0x7f; + bool huffman_encoded = (h_and_prefix & 0x80) == 0x80; + cb->OnStringStart(huffman_encoded, length); + if (length <= db->Remaining()) { + // Yeah, we've got the whole thing in the decode buffer. + // Ideally this will be the common case. Note that we don't + // update any of the member variables in this path. + cb->OnStringData(db->cursor(), length); + db->AdvanceCursor(length); + cb->OnStringEnd(); + return DecodeStatus::kDecodeDone; + } + // Not all in the buffer. + huffman_encoded_ = huffman_encoded; + remaining_ = length; + // Call Resume to decode the string body, which is only partially + // in the decode buffer (or not at all). + state_ = kDecodingString; + return Resume(db, cb); + } + // Call Resume to decode the string length, which is either not in + // the decode buffer, or spans multiple bytes. + state_ = kStartDecodingLength; + return Resume(db, cb); + } + + template <class Listener> + DecodeStatus Resume(DecodeBuffer* db, Listener* cb) { + DecodeStatus status; + while (true) { + switch (state_) { + case kStartDecodingLength: + HTTP2_DVLOG(2) << "kStartDecodingLength: db->Remaining=" + << db->Remaining(); + if (!StartDecodingLength(db, cb, &status)) { + // The length is split across decode buffers. + return status; + } + // We've finished decoding the length, which spanned one or more + // bytes. Approximately 17% of strings have a length that is greater + // than 126 bytes, and thus the length is encoded in more than one + // byte, and so doesn't get the benefit of the optimization in + // Start() for single byte lengths. But, we still expect that most + // of such strings will be contained entirely in a single decode + // buffer, and hence this fall through skips another trip through the + // switch above and more importantly skips setting the state_ variable + // again in those cases where we don't need it. + ABSL_FALLTHROUGH_INTENDED; + + case kDecodingString: + HTTP2_DVLOG(2) << "kDecodingString: db->Remaining=" << db->Remaining() + << " remaining_=" << remaining_; + return DecodeString(db, cb); + + case kResumeDecodingLength: + HTTP2_DVLOG(2) << "kResumeDecodingLength: db->Remaining=" + << db->Remaining(); + if (!ResumeDecodingLength(db, cb, &status)) { + return status; + } + } + } + } + + std::string DebugString() const; + + private: + static std::string StateToString(StringDecoderState v); + + // Returns true if the length is fully decoded and the listener wants the + // decoding to continue, false otherwise; status is set to the status from + // the varint decoder. + // If the length is not fully decoded, case state_ is set appropriately + // for the next call to Resume. + template <class Listener> + bool StartDecodingLength(DecodeBuffer* db, + Listener* cb, + DecodeStatus* status) { + if (db->Empty()) { + *status = DecodeStatus::kDecodeInProgress; + state_ = kStartDecodingLength; + return false; + } + uint8_t h_and_prefix = db->DecodeUInt8(); + huffman_encoded_ = (h_and_prefix & 0x80) == 0x80; + *status = length_decoder_.Start(h_and_prefix, 7, db); + if (*status == DecodeStatus::kDecodeDone) { + OnStringStart(cb, status); + return true; + } + // Set the state to cover the DecodeStatus::kDecodeInProgress case. + // Won't be needed if the status is kDecodeError. + state_ = kResumeDecodingLength; + return false; + } + + // Returns true if the length is fully decoded and the listener wants the + // decoding to continue, false otherwise; status is set to the status from + // the varint decoder; state_ is updated when fully decoded. + // If the length is not fully decoded, case state_ is set appropriately + // for the next call to Resume. + template <class Listener> + bool ResumeDecodingLength(DecodeBuffer* db, + Listener* cb, + DecodeStatus* status) { + QUICHE_DCHECK_EQ(state_, kResumeDecodingLength); + *status = length_decoder_.Resume(db); + if (*status == DecodeStatus::kDecodeDone) { + state_ = kDecodingString; + OnStringStart(cb, status); + return true; + } + return false; + } + + // Returns true if the listener wants the decoding to continue, and + // false otherwise, in which case status set. + template <class Listener> + void OnStringStart(Listener* cb, DecodeStatus* /*status*/) { + // TODO(vasilvv): fail explicitly in case of truncation. + remaining_ = static_cast<size_t>(length_decoder_.value()); + // Make callback so consumer knows what is coming. + cb->OnStringStart(huffman_encoded_, remaining_); + } + + // Passes the available portion of the string to the listener, and signals + // the end of the string when it is reached. Returns kDecodeDone or + // kDecodeInProgress as appropriate. + template <class Listener> + DecodeStatus DecodeString(DecodeBuffer* db, Listener* cb) { + size_t len = std::min(remaining_, db->Remaining()); + if (len > 0) { + cb->OnStringData(db->cursor(), len); + db->AdvanceCursor(len); + remaining_ -= len; + } + if (remaining_ == 0) { + cb->OnStringEnd(); + return DecodeStatus::kDecodeDone; + } + state_ = kDecodingString; + return DecodeStatus::kDecodeInProgress; + } + + HpackVarintDecoder length_decoder_; + + // These fields are initialized just to keep ASAN happy about reading + // them from DebugString(). + size_t remaining_ = 0; + StringDecoderState state_ = kStartDecodingLength; + bool huffman_encoded_ = false; +}; + +QUICHE_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const HpackStringDecoder& v); + +} // namespace http2 +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_decoder_listener.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_decoder_listener.cc new file mode 100644 index 00000000000..fef70c28e9d --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_decoder_listener.cc @@ -0,0 +1,36 @@ +// 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/hpack/decoder/hpack_string_decoder_listener.h" + +#include "quiche/http2/platform/api/http2_logging.h" + +namespace http2 { +namespace test { + +void HpackStringDecoderVLoggingListener::OnStringStart(bool huffman_encoded, + size_t len) { + HTTP2_VLOG(1) << "OnStringStart: H=" << huffman_encoded << ", len=" << len; + if (wrapped_) { + wrapped_->OnStringStart(huffman_encoded, len); + } +} + +void HpackStringDecoderVLoggingListener::OnStringData(const char* data, + size_t len) { + HTTP2_VLOG(1) << "OnStringData: len=" << len; + if (wrapped_) { + return wrapped_->OnStringData(data, len); + } +} + +void HpackStringDecoderVLoggingListener::OnStringEnd() { + HTTP2_VLOG(1) << "OnStringEnd"; + if (wrapped_) { + return wrapped_->OnStringEnd(); + } +} + +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_decoder_listener.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_decoder_listener.h new file mode 100644 index 00000000000..c343d407e7c --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_decoder_listener.h @@ -0,0 +1,62 @@ +// 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_HPACK_DECODER_HPACK_STRING_DECODER_LISTENER_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_LISTENER_H_ + +// Defines HpackStringDecoderListener which defines the methods required by an +// HpackStringDecoder. Also defines HpackStringDecoderVLoggingListener which +// logs before calling another HpackStringDecoderListener implementation. +// For now these are only used by tests, so placed in the test namespace. + +#include <stddef.h> + +#include "quiche/common/platform/api/quiche_export.h" + +namespace http2 { +namespace test { + +// HpackStringDecoder methods require a listener that implements the methods +// below, but it is NOT necessary to extend this class because the methods +// are templates. +class QUICHE_EXPORT_PRIVATE HpackStringDecoderListener { + public: + virtual ~HpackStringDecoderListener() {} + + // Called at the start of decoding an HPACK string. The encoded length of the + // string is |len| bytes, which may be zero. The string is Huffman encoded + // if huffman_encoded is true, else it is plain text (i.e. the encoded length + // is then the plain text length). + virtual void OnStringStart(bool huffman_encoded, size_t len) = 0; + + // Called when some data is available, or once when the string length is zero + // (to simplify the decoder, it doesn't have a special case for len==0). + virtual void OnStringData(const char* data, size_t len) = 0; + + // Called after OnStringData has provided all of the encoded bytes of the + // string. + virtual void OnStringEnd() = 0; +}; + +class QUICHE_EXPORT_PRIVATE HpackStringDecoderVLoggingListener + : public HpackStringDecoderListener { + public: + HpackStringDecoderVLoggingListener() : wrapped_(nullptr) {} + explicit HpackStringDecoderVLoggingListener( + HpackStringDecoderListener* wrapped) + : wrapped_(wrapped) {} + ~HpackStringDecoderVLoggingListener() override {} + + void OnStringStart(bool huffman_encoded, size_t len) override; + void OnStringData(const char* data, size_t len) override; + void OnStringEnd() override; + + private: + HpackStringDecoderListener* const wrapped_; +}; + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_LISTENER_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_decoder_test.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_decoder_test.cc new file mode 100644 index 00000000000..cfdb20ab147 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_decoder_test.cc @@ -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. + +#include "quiche/http2/hpack/decoder/hpack_string_decoder.h" + +// Tests of HpackStringDecoder. + +#include "absl/strings/string_view.h" +#include "quiche/http2/hpack/decoder/hpack_string_collector.h" +#include "quiche/http2/hpack/decoder/hpack_string_decoder_listener.h" +#include "quiche/http2/hpack/tools/hpack_block_builder.h" +#include "quiche/http2/test_tools/http2_random.h" +#include "quiche/http2/tools/random_decoder_test.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/common/platform/api/quiche_test_helpers.h" + +namespace http2 { +namespace test { +namespace { + +const bool kMayReturnZeroOnFirst = false; +const bool kCompressed = true; +const bool kUncompressed = false; + +class HpackStringDecoderTest : public RandomDecoderTest { + protected: + HpackStringDecoderTest() : listener_(&collector_) {} + + DecodeStatus StartDecoding(DecodeBuffer* b) override { + ++start_decoding_calls_; + collector_.Clear(); + return decoder_.Start(b, &listener_); + } + + DecodeStatus ResumeDecoding(DecodeBuffer* b) override { + // Provides coverage of DebugString and StateToString. + // Not validating output. + HTTP2_VLOG(1) << decoder_.DebugString(); + HTTP2_VLOG(2) << collector_; + return decoder_.Resume(b, &listener_); + } + + AssertionResult Collected(absl::string_view s, bool huffman_encoded) { + HTTP2_VLOG(1) << collector_; + return collector_.Collected(s, huffman_encoded); + } + + // expected_str is a std::string rather than a const std::string& or + // absl::string_view so that the lambda makes a copy of the string, and thus + // the string to be passed to Collected outlives the call to MakeValidator. + Validator MakeValidator(const std::string& expected_str, + bool expected_huffman) { + return + [expected_str, expected_huffman, this]( + const DecodeBuffer& /*input*/, + DecodeStatus /*status*/) -> AssertionResult { + AssertionResult result = Collected(expected_str, expected_huffman); + if (result) { + VERIFY_EQ(collector_, + HpackStringCollector(expected_str, expected_huffman)); + } else { + VERIFY_NE(collector_, + HpackStringCollector(expected_str, expected_huffman)); + } + HTTP2_VLOG(2) << collector_.ToString(); + collector_.Clear(); + HTTP2_VLOG(2) << collector_; + return result; + }; + } + + HpackStringDecoder decoder_; + HpackStringCollector collector_; + HpackStringDecoderVLoggingListener listener_; + size_t start_decoding_calls_ = 0; +}; + +TEST_F(HpackStringDecoderTest, DecodeEmptyString) { + { + Validator validator = ValidateDoneAndEmpty(MakeValidator("", kCompressed)); + const char kData[] = {'\x80'}; + DecodeBuffer b(kData); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator)); + } + { + // Make sure it stops after decoding the empty string. + Validator validator = + ValidateDoneAndOffset(1, MakeValidator("", kUncompressed)); + const char kData[] = {'\x00', '\xff'}; + DecodeBuffer b(kData); + EXPECT_EQ(2u, b.Remaining()); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator)); + EXPECT_EQ(1u, b.Remaining()); + } +} + +TEST_F(HpackStringDecoderTest, DecodeShortString) { + { + // Make sure it stops after decoding the non-empty string. + Validator validator = + ValidateDoneAndOffset(11, MakeValidator("start end.", kCompressed)); + const char kData[] = "\x8astart end.Don't peek at this."; + DecodeBuffer b(kData); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator)); + } + { + Validator validator = + ValidateDoneAndOffset(11, MakeValidator("start end.", kUncompressed)); + absl::string_view data("\x0astart end."); + DecodeBuffer b(data); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator)); + } +} + +TEST_F(HpackStringDecoderTest, DecodeLongStrings) { + std::string name = Random().RandString(1024); + std::string value = Random().RandString(65536); + HpackBlockBuilder hbb; + + hbb.AppendString(false, name); + uint32_t offset_after_name = hbb.size(); + EXPECT_EQ(3 + name.size(), offset_after_name); + + hbb.AppendString(true, value); + uint32_t offset_after_value = hbb.size(); + EXPECT_EQ(3 + name.size() + 4 + value.size(), offset_after_value); + + DecodeBuffer b(hbb.buffer()); + + // Decode the name... + EXPECT_TRUE(DecodeAndValidateSeveralWays( + &b, kMayReturnZeroOnFirst, + ValidateDoneAndOffset(offset_after_name, + MakeValidator(name, kUncompressed)))); + EXPECT_EQ(offset_after_name, b.Offset()); + EXPECT_EQ(offset_after_value - offset_after_name, b.Remaining()); + + // Decode the value... + EXPECT_TRUE(DecodeAndValidateSeveralWays( + &b, kMayReturnZeroOnFirst, + ValidateDoneAndOffset(offset_after_value - offset_after_name, + MakeValidator(value, kCompressed)))); + EXPECT_EQ(offset_after_value, b.Offset()); + EXPECT_EQ(0u, b.Remaining()); +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_whole_entry_buffer.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_whole_entry_buffer.cc new file mode 100644 index 00000000000..3bb9652a1c9 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_whole_entry_buffer.cc @@ -0,0 +1,153 @@ +// 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/hpack/decoder/hpack_whole_entry_buffer.h" + +#include "absl/strings/str_cat.h" +#include "quiche/http2/platform/api/http2_flag_utils.h" +#include "quiche/http2/platform/api/http2_flags.h" +#include "quiche/http2/platform/api/http2_logging.h" +#include "quiche/common/platform/api/quiche_logging.h" +#include "quiche/common/quiche_text_utils.h" + +namespace http2 { + +HpackWholeEntryBuffer::HpackWholeEntryBuffer(HpackWholeEntryListener* listener, + size_t max_string_size_bytes) + : max_string_size_bytes_(max_string_size_bytes) { + set_listener(listener); +} +HpackWholeEntryBuffer::~HpackWholeEntryBuffer() = default; + +void HpackWholeEntryBuffer::set_listener(HpackWholeEntryListener* listener) { + QUICHE_CHECK(listener); + listener_ = listener; +} + +void HpackWholeEntryBuffer::set_max_string_size_bytes( + size_t max_string_size_bytes) { + max_string_size_bytes_ = max_string_size_bytes; +} + +void HpackWholeEntryBuffer::BufferStringsIfUnbuffered() { + name_.BufferStringIfUnbuffered(); + value_.BufferStringIfUnbuffered(); +} + +void HpackWholeEntryBuffer::OnIndexedHeader(size_t index) { + HTTP2_DVLOG(2) << "HpackWholeEntryBuffer::OnIndexedHeader: index=" << index; + listener_->OnIndexedHeader(index); +} + +void HpackWholeEntryBuffer::OnStartLiteralHeader(HpackEntryType entry_type, + size_t maybe_name_index) { + HTTP2_DVLOG(2) << "HpackWholeEntryBuffer::OnStartLiteralHeader: entry_type=" + << entry_type << ", maybe_name_index=" << maybe_name_index; + entry_type_ = entry_type; + maybe_name_index_ = maybe_name_index; +} + +void HpackWholeEntryBuffer::OnNameStart(bool huffman_encoded, size_t len) { + HTTP2_DVLOG(2) << "HpackWholeEntryBuffer::OnNameStart: huffman_encoded=" + << (huffman_encoded ? "true" : "false") << ", len=" << len; + QUICHE_DCHECK_EQ(maybe_name_index_, 0u); + if (!error_detected_) { + if (len > max_string_size_bytes_) { + HTTP2_DVLOG(1) << "Name length (" << len << ") is longer than permitted (" + << max_string_size_bytes_ << ")"; + ReportError(HpackDecodingError::kNameTooLong, ""); + HTTP2_CODE_COUNT_N(decompress_failure_3, 18, 23); + return; + } + name_.OnStart(huffman_encoded, len); + } +} + +void HpackWholeEntryBuffer::OnNameData(const char* data, size_t len) { + HTTP2_DVLOG(2) << "HpackWholeEntryBuffer::OnNameData: len=" << len + << " data:\n" + << quiche::QuicheTextUtils::HexDump( + absl::string_view(data, len)); + QUICHE_DCHECK_EQ(maybe_name_index_, 0u); + if (!error_detected_ && !name_.OnData(data, len)) { + ReportError(HpackDecodingError::kNameHuffmanError, ""); + HTTP2_CODE_COUNT_N(decompress_failure_3, 19, 23); + } +} + +void HpackWholeEntryBuffer::OnNameEnd() { + HTTP2_DVLOG(2) << "HpackWholeEntryBuffer::OnNameEnd"; + QUICHE_DCHECK_EQ(maybe_name_index_, 0u); + if (!error_detected_ && !name_.OnEnd()) { + ReportError(HpackDecodingError::kNameHuffmanError, ""); + HTTP2_CODE_COUNT_N(decompress_failure_3, 20, 23); + } +} + +void HpackWholeEntryBuffer::OnValueStart(bool huffman_encoded, size_t len) { + HTTP2_DVLOG(2) << "HpackWholeEntryBuffer::OnValueStart: huffman_encoded=" + << (huffman_encoded ? "true" : "false") << ", len=" << len; + if (!error_detected_) { + if (len > max_string_size_bytes_) { + std::string detailed_error = absl::StrCat( + "Value length (", len, ") of [", name_.GetStringIfComplete(), + "] is longer than permitted (", max_string_size_bytes_, ")"); + HTTP2_DVLOG(1) << detailed_error; + ReportError(HpackDecodingError::kValueTooLong, detailed_error); + HTTP2_CODE_COUNT_N(decompress_failure_3, 21, 23); + return; + } + value_.OnStart(huffman_encoded, len); + } +} + +void HpackWholeEntryBuffer::OnValueData(const char* data, size_t len) { + HTTP2_DVLOG(2) << "HpackWholeEntryBuffer::OnValueData: len=" << len + << " data:\n" + << quiche::QuicheTextUtils::HexDump( + absl::string_view(data, len)); + if (!error_detected_ && !value_.OnData(data, len)) { + ReportError(HpackDecodingError::kValueHuffmanError, ""); + HTTP2_CODE_COUNT_N(decompress_failure_3, 22, 23); + } +} + +void HpackWholeEntryBuffer::OnValueEnd() { + HTTP2_DVLOG(2) << "HpackWholeEntryBuffer::OnValueEnd"; + if (error_detected_) { + return; + } + if (!value_.OnEnd()) { + ReportError(HpackDecodingError::kValueHuffmanError, ""); + HTTP2_CODE_COUNT_N(decompress_failure_3, 23, 23); + return; + } + if (maybe_name_index_ == 0) { + listener_->OnLiteralNameAndValue(entry_type_, &name_, &value_); + name_.Reset(); + } else { + listener_->OnNameIndexAndLiteralValue(entry_type_, maybe_name_index_, + &value_); + } + value_.Reset(); +} + +void HpackWholeEntryBuffer::OnDynamicTableSizeUpdate(size_t size) { + HTTP2_DVLOG(2) << "HpackWholeEntryBuffer::OnDynamicTableSizeUpdate: size=" + << size; + listener_->OnDynamicTableSizeUpdate(size); +} + +void HpackWholeEntryBuffer::ReportError(HpackDecodingError error, + std::string detailed_error) { + if (!error_detected_) { + HTTP2_DVLOG(1) << "HpackWholeEntryBuffer::ReportError: " + << HpackDecodingErrorToString(error); + error_detected_ = true; + listener_->OnHpackDecodeError(error, detailed_error); + listener_ = HpackWholeEntryNoOpListener::NoOpListener(); + } +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_whole_entry_buffer.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_whole_entry_buffer.h new file mode 100644 index 00000000000..0cdde65e108 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_whole_entry_buffer.h @@ -0,0 +1,102 @@ +// 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_HPACK_DECODER_HPACK_WHOLE_ENTRY_BUFFER_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_BUFFER_H_ + +// HpackWholeEntryBuffer isolates a listener from the fact that an entry may +// be split across multiple input buffers, providing one callback per entry. +// HpackWholeEntryBuffer requires that the HpackEntryDecoderListener be made in +// the correct order, which is tested by hpack_entry_decoder_test.cc. + +#include <stddef.h> + +#include "absl/strings/string_view.h" +#include "quiche/http2/hpack/decoder/hpack_decoder_string_buffer.h" +#include "quiche/http2/hpack/decoder/hpack_decoding_error.h" +#include "quiche/http2/hpack/decoder/hpack_entry_decoder_listener.h" +#include "quiche/http2/hpack/decoder/hpack_whole_entry_listener.h" +#include "quiche/http2/hpack/http2_hpack_constants.h" +#include "quiche/common/platform/api/quiche_export.h" + +namespace http2 { + +// TODO(jamessynge): Consider renaming HpackEntryDecoderListener to +// HpackEntryPartsListener or HpackEntryFragmentsListener. +class QUICHE_EXPORT_PRIVATE HpackWholeEntryBuffer + : public HpackEntryDecoderListener { + public: + // max_string_size specifies the maximum size of an on-the-wire string (name + // or value, plain or Huffman encoded) that will be accepted. See sections + // 5.1 and 5.2 of RFC 7541. This is a defense against OOM attacks; HTTP/2 + // allows a decoder to enforce any limit of the size of the header lists + // that it is willing decode, including less than the MAX_HEADER_LIST_SIZE + // setting, a setting that is initially unlimited. For example, we might + // choose to send a MAX_HEADER_LIST_SIZE of 64KB, and to use that same value + // as the upper bound for individual strings. + HpackWholeEntryBuffer(HpackWholeEntryListener* listener, + size_t max_string_size); + ~HpackWholeEntryBuffer() override; + + HpackWholeEntryBuffer(const HpackWholeEntryBuffer&) = delete; + HpackWholeEntryBuffer& operator=(const HpackWholeEntryBuffer&) = delete; + + // Set the listener to be notified when a whole entry has been decoded. + // The listener may be changed at any time. + void set_listener(HpackWholeEntryListener* listener); + + // Set how much encoded data this decoder is willing to buffer. + // TODO(jamessynge): Come up with consistent semantics for this protection + // across the various decoders; e.g. should it be for a single string or + // a single header entry? + void set_max_string_size_bytes(size_t max_string_size_bytes); + + // Ensure that decoded strings pointed to by the HpackDecoderStringBuffer + // instances name_ and value_ are buffered, which allows any underlying + // transport buffer to be freed or reused without overwriting the decoded + // strings. This is needed only when an HPACK entry is split across transport + // buffers. See HpackDecoder::DecodeFragment. + void BufferStringsIfUnbuffered(); + + // Was an error detected? After an error has been detected and reported, + // no further callbacks will be made to the listener. + bool error_detected() const { return error_detected_; } + + // Implement the HpackEntryDecoderListener methods. + + void OnIndexedHeader(size_t index) override; + void OnStartLiteralHeader(HpackEntryType entry_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; + + private: + void ReportError(HpackDecodingError error, std::string detailed_error); + + HpackWholeEntryListener* listener_; + HpackDecoderStringBuffer name_, value_; + + // max_string_size_bytes_ specifies the maximum allowed size of an on-the-wire + // string. Larger strings will be reported as errors to the listener; the + // endpoint should treat these as COMPRESSION errors, which are CONNECTION + // level errors. + size_t max_string_size_bytes_; + + // The name index (or zero) of the current header entry with a literal value. + size_t maybe_name_index_; + + // The type of the current header entry (with literals) that is being decoded. + HpackEntryType entry_type_; + + bool error_detected_ = false; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_BUFFER_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc new file mode 100644 index 00000000000..5c8ba808ac1 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc @@ -0,0 +1,231 @@ +// 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/hpack/decoder/hpack_whole_entry_buffer.h" + +// Tests of HpackWholeEntryBuffer: does it buffer correctly, and does it +// detect Huffman decoding errors and oversize string errors? + +#include "quiche/common/platform/api/quiche_test.h" + +using ::testing::_; +using ::testing::AllOf; +using ::testing::InSequence; +using ::testing::Property; +using ::testing::StrictMock; + +namespace http2 { +namespace test { +namespace { + +constexpr size_t kMaxStringSize = 20; + +class MockHpackWholeEntryListener : public HpackWholeEntryListener { + public: + ~MockHpackWholeEntryListener() override = default; + + MOCK_METHOD(void, OnIndexedHeader, (size_t index), (override)); + MOCK_METHOD(void, + OnNameIndexAndLiteralValue, + (HpackEntryType entry_type, + size_t name_index, + HpackDecoderStringBuffer* value_buffer), + (override)); + MOCK_METHOD(void, + OnLiteralNameAndValue, + (HpackEntryType entry_type, + HpackDecoderStringBuffer* name_buffer, + HpackDecoderStringBuffer* value_buffer), + (override)); + MOCK_METHOD(void, OnDynamicTableSizeUpdate, (size_t size), (override)); + MOCK_METHOD(void, + OnHpackDecodeError, + (HpackDecodingError error, std::string detailed_error), + (override)); +}; + +class HpackWholeEntryBufferTest : public QuicheTest { + protected: + HpackWholeEntryBufferTest() : entry_buffer_(&listener_, kMaxStringSize) {} + ~HpackWholeEntryBufferTest() override = default; + + StrictMock<MockHpackWholeEntryListener> listener_; + HpackWholeEntryBuffer entry_buffer_; +}; + +// OnIndexedHeader is an immediate pass through. +TEST_F(HpackWholeEntryBufferTest, OnIndexedHeader) { + { + InSequence seq; + EXPECT_CALL(listener_, OnIndexedHeader(17)); + entry_buffer_.OnIndexedHeader(17); + } + { + InSequence seq; + EXPECT_CALL(listener_, OnIndexedHeader(62)); + entry_buffer_.OnIndexedHeader(62); + } + { + InSequence seq; + EXPECT_CALL(listener_, OnIndexedHeader(62)); + entry_buffer_.OnIndexedHeader(62); + } + { + InSequence seq; + EXPECT_CALL(listener_, OnIndexedHeader(128)); + entry_buffer_.OnIndexedHeader(128); + } + StrictMock<MockHpackWholeEntryListener> listener2; + entry_buffer_.set_listener(&listener2); + { + InSequence seq; + EXPECT_CALL(listener2, OnIndexedHeader(100)); + entry_buffer_.OnIndexedHeader(100); + } +} + +// OnDynamicTableSizeUpdate is an immediate pass through. +TEST_F(HpackWholeEntryBufferTest, OnDynamicTableSizeUpdate) { + { + InSequence seq; + EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(4096)); + entry_buffer_.OnDynamicTableSizeUpdate(4096); + } + { + InSequence seq; + EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(0)); + entry_buffer_.OnDynamicTableSizeUpdate(0); + } + { + InSequence seq; + EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(1024)); + entry_buffer_.OnDynamicTableSizeUpdate(1024); + } + { + InSequence seq; + EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(1024)); + entry_buffer_.OnDynamicTableSizeUpdate(1024); + } + StrictMock<MockHpackWholeEntryListener> listener2; + entry_buffer_.set_listener(&listener2); + { + InSequence seq; + EXPECT_CALL(listener2, OnDynamicTableSizeUpdate(0)); + entry_buffer_.OnDynamicTableSizeUpdate(0); + } +} + +TEST_F(HpackWholeEntryBufferTest, OnNameIndexAndLiteralValue) { + entry_buffer_.OnStartLiteralHeader(HpackEntryType::kNeverIndexedLiteralHeader, + 123); + entry_buffer_.OnValueStart(false, 10); + entry_buffer_.OnValueData("some data.", 10); + + // Force the value to be buffered. + entry_buffer_.BufferStringsIfUnbuffered(); + + EXPECT_CALL( + listener_, + OnNameIndexAndLiteralValue( + HpackEntryType::kNeverIndexedLiteralHeader, 123, + AllOf(Property(&HpackDecoderStringBuffer::str, "some data."), + Property(&HpackDecoderStringBuffer::BufferedLength, 10)))); + + entry_buffer_.OnValueEnd(); +} + +TEST_F(HpackWholeEntryBufferTest, OnLiteralNameAndValue) { + entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 0); + // Force the name to be buffered by delivering it in two pieces. + entry_buffer_.OnNameStart(false, 9); + entry_buffer_.OnNameData("some-", 5); + entry_buffer_.OnNameData("name", 4); + entry_buffer_.OnNameEnd(); + entry_buffer_.OnValueStart(false, 12); + entry_buffer_.OnValueData("Header Value", 12); + + EXPECT_CALL( + listener_, + OnLiteralNameAndValue( + HpackEntryType::kIndexedLiteralHeader, + AllOf(Property(&HpackDecoderStringBuffer::str, "some-name"), + Property(&HpackDecoderStringBuffer::BufferedLength, 9)), + AllOf(Property(&HpackDecoderStringBuffer::str, "Header Value"), + Property(&HpackDecoderStringBuffer::BufferedLength, 0)))); + + entry_buffer_.OnValueEnd(); +} + +// Verify that a name longer than the allowed size generates an error. +TEST_F(HpackWholeEntryBufferTest, NameTooLong) { + entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 0); + EXPECT_CALL(listener_, + OnHpackDecodeError(HpackDecodingError::kNameTooLong, _)); + entry_buffer_.OnNameStart(false, kMaxStringSize + 1); +} + +// Verify that a value longer than the allowed size generates an error. +TEST_F(HpackWholeEntryBufferTest, ValueTooLong) { + entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 0); + EXPECT_CALL(listener_, + OnHpackDecodeError( + HpackDecodingError::kValueTooLong, + "Value length (21) of [path] is longer than permitted (20)")); + entry_buffer_.OnNameStart(false, 4); + entry_buffer_.OnNameData("path", 4); + entry_buffer_.OnNameEnd(); + entry_buffer_.OnValueStart(false, kMaxStringSize + 1); +} + +// Regression test for b/162141899. +TEST_F(HpackWholeEntryBufferTest, ValueTooLongWithoutName) { + entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 1); + EXPECT_CALL(listener_, + OnHpackDecodeError( + HpackDecodingError::kValueTooLong, + "Value length (21) of [] is longer than permitted (20)")); + entry_buffer_.OnValueStart(false, kMaxStringSize + 1); +} + +// Verify that a Huffman encoded name with an explicit EOS generates an error +// for an explicit EOS. +TEST_F(HpackWholeEntryBufferTest, NameHuffmanError) { + const char data[] = "\xff\xff\xff"; + entry_buffer_.OnStartLiteralHeader(HpackEntryType::kUnindexedLiteralHeader, + 0); + entry_buffer_.OnNameStart(true, 4); + entry_buffer_.OnNameData(data, 3); + + EXPECT_CALL(listener_, + OnHpackDecodeError(HpackDecodingError::kNameHuffmanError, _)); + + entry_buffer_.OnNameData(data, 1); + + // After an error is reported, the listener is not called again. + EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(8096)).Times(0); + entry_buffer_.OnDynamicTableSizeUpdate(8096); +} + +// Verify that a Huffman encoded value that isn't properly terminated with +// a partial EOS symbol generates an error. +TEST_F(HpackWholeEntryBufferTest, ValueHuffmanError) { + const char data[] = "\x00\x00\x00"; + entry_buffer_.OnStartLiteralHeader(HpackEntryType::kNeverIndexedLiteralHeader, + 61); + entry_buffer_.OnValueStart(true, 3); + entry_buffer_.OnValueData(data, 3); + + EXPECT_CALL(listener_, + OnHpackDecodeError(HpackDecodingError::kValueHuffmanError, _)); + + entry_buffer_.OnValueEnd(); + + // After an error is reported, the listener is not called again. + EXPECT_CALL(listener_, OnIndexedHeader(17)).Times(0); + entry_buffer_.OnIndexedHeader(17); +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_whole_entry_listener.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_whole_entry_listener.cc new file mode 100644 index 00000000000..cafd6ecbe84 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_whole_entry_listener.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/hpack/decoder/hpack_whole_entry_listener.h" + +namespace http2 { + +HpackWholeEntryListener::~HpackWholeEntryListener() = default; + +HpackWholeEntryNoOpListener::~HpackWholeEntryNoOpListener() = default; + +void HpackWholeEntryNoOpListener::OnIndexedHeader(size_t /*index*/) {} +void HpackWholeEntryNoOpListener::OnNameIndexAndLiteralValue( + HpackEntryType /*entry_type*/, + size_t /*name_index*/, + HpackDecoderStringBuffer* /*value_buffer*/) {} +void HpackWholeEntryNoOpListener::OnLiteralNameAndValue( + HpackEntryType /*entry_type*/, + HpackDecoderStringBuffer* /*name_buffer*/, + HpackDecoderStringBuffer* /*value_buffer*/) {} +void HpackWholeEntryNoOpListener::OnDynamicTableSizeUpdate(size_t /*size*/) {} +void HpackWholeEntryNoOpListener::OnHpackDecodeError( + HpackDecodingError /*error*/, + std::string /*detailed_error*/) {} + +// static +HpackWholeEntryNoOpListener* HpackWholeEntryNoOpListener::NoOpListener() { + static HpackWholeEntryNoOpListener* static_instance = + new HpackWholeEntryNoOpListener(); + return static_instance; +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_whole_entry_listener.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_whole_entry_listener.h new file mode 100644 index 00000000000..90c6f9d6da4 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_whole_entry_listener.h @@ -0,0 +1,83 @@ +// 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. + +// Defines HpackWholeEntryListener, the base class of listeners for decoded +// complete HPACK entries, as opposed to HpackEntryDecoderListener which +// receives multiple callbacks for some single entries. + +#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_LISTENER_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_LISTENER_H_ + +#include <stddef.h> + +#include "absl/strings/string_view.h" +#include "quiche/http2/hpack/decoder/hpack_decoder_string_buffer.h" +#include "quiche/http2/hpack/decoder/hpack_decoding_error.h" +#include "quiche/http2/hpack/http2_hpack_constants.h" +#include "quiche/common/platform/api/quiche_export.h" + +namespace http2 { + +class QUICHE_EXPORT_PRIVATE HpackWholeEntryListener { + public: + virtual ~HpackWholeEntryListener(); + + // Called when an indexed header (i.e. one in the static or dynamic table) has + // been decoded from an HPACK block. index is supposed to be non-zero, but + // that has not been checked by the caller. + virtual void OnIndexedHeader(size_t index) = 0; + + // Called when a header entry with a name index and literal value has + // been fully decoded from an HPACK block. name_index is NOT zero. + // entry_type will be kIndexedLiteralHeader, kUnindexedLiteralHeader, or + // kNeverIndexedLiteralHeader. + virtual void OnNameIndexAndLiteralValue( + HpackEntryType entry_type, + size_t name_index, + HpackDecoderStringBuffer* value_buffer) = 0; + + // Called when a header entry with a literal name and literal value + // has been fully decoded from an HPACK block. entry_type will be + // kIndexedLiteralHeader, kUnindexedLiteralHeader, or + // kNeverIndexedLiteralHeader. + virtual void OnLiteralNameAndValue( + HpackEntryType entry_type, + HpackDecoderStringBuffer* name_buffer, + HpackDecoderStringBuffer* value_buffer) = 0; + + // Called when an update to the size of the peer's dynamic table has been + // decoded. + virtual void OnDynamicTableSizeUpdate(size_t size) = 0; + + // OnHpackDecodeError is called if an error is detected while decoding. + virtual void OnHpackDecodeError(HpackDecodingError error, + std::string detailed_error) = 0; +}; + +// A no-op implementation of HpackWholeEntryDecoderListener, useful for ignoring +// callbacks once an error is detected. +class QUICHE_EXPORT_PRIVATE HpackWholeEntryNoOpListener + : public HpackWholeEntryListener { + public: + ~HpackWholeEntryNoOpListener() override; + + void OnIndexedHeader(size_t index) override; + void OnNameIndexAndLiteralValue( + HpackEntryType entry_type, + size_t name_index, + HpackDecoderStringBuffer* value_buffer) override; + void OnLiteralNameAndValue(HpackEntryType entry_type, + HpackDecoderStringBuffer* name_buffer, + HpackDecoderStringBuffer* value_buffer) override; + void OnDynamicTableSizeUpdate(size_t size) override; + void OnHpackDecodeError(HpackDecodingError error, + std::string detailed_error) override; + + // Returns a listener that ignores all the calls. + static HpackWholeEntryNoOpListener* NoOpListener(); +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_LISTENER_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/hpack_static_table_entries.inc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/hpack_static_table_entries.inc new file mode 100644 index 00000000000..c6ae125f3b2 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/hpack_static_table_entries.inc @@ -0,0 +1,65 @@ +// This file is designed to be included by C/C++ files which need the contents +// of the HPACK static table. It may be included more than once if necessary. +// See http://httpwg.org/specs/rfc7541.html#static.table.definition + +STATIC_TABLE_ENTRY(":authority", "", 1); +STATIC_TABLE_ENTRY(":method", "GET", 2); +STATIC_TABLE_ENTRY(":method", "POST", 3); +STATIC_TABLE_ENTRY(":path", "/", 4); +STATIC_TABLE_ENTRY(":path", "/index.html", 5); +STATIC_TABLE_ENTRY(":scheme", "http", 6); +STATIC_TABLE_ENTRY(":scheme", "https", 7); +STATIC_TABLE_ENTRY(":status", "200", 8); +STATIC_TABLE_ENTRY(":status", "204", 9); +STATIC_TABLE_ENTRY(":status", "206", 10); +STATIC_TABLE_ENTRY(":status", "304", 11); +STATIC_TABLE_ENTRY(":status", "400", 12); +STATIC_TABLE_ENTRY(":status", "404", 13); +STATIC_TABLE_ENTRY(":status", "500", 14); +STATIC_TABLE_ENTRY("accept-charset", "", 15); +STATIC_TABLE_ENTRY("accept-encoding", "gzip, deflate", 16); +STATIC_TABLE_ENTRY("accept-language", "", 17); +STATIC_TABLE_ENTRY("accept-ranges", "", 18); +STATIC_TABLE_ENTRY("accept", "", 19); +STATIC_TABLE_ENTRY("access-control-allow-origin", "", 20); +STATIC_TABLE_ENTRY("age", "", 21); +STATIC_TABLE_ENTRY("allow", "", 22); +STATIC_TABLE_ENTRY("authorization", "", 23); +STATIC_TABLE_ENTRY("cache-control", "", 24); +STATIC_TABLE_ENTRY("content-disposition", "", 25); +STATIC_TABLE_ENTRY("content-encoding", "", 26); +STATIC_TABLE_ENTRY("content-language", "", 27); +STATIC_TABLE_ENTRY("content-length", "", 28); +STATIC_TABLE_ENTRY("content-location", "", 29); +STATIC_TABLE_ENTRY("content-range", "", 30); +STATIC_TABLE_ENTRY("content-type", "", 31); +STATIC_TABLE_ENTRY("cookie", "", 32); +STATIC_TABLE_ENTRY("date", "", 33); +STATIC_TABLE_ENTRY("etag", "", 34); +STATIC_TABLE_ENTRY("expect", "", 35); +STATIC_TABLE_ENTRY("expires", "", 36); +STATIC_TABLE_ENTRY("from", "", 37); +STATIC_TABLE_ENTRY("host", "", 38); +STATIC_TABLE_ENTRY("if-match", "", 39); +STATIC_TABLE_ENTRY("if-modified-since", "", 40); +STATIC_TABLE_ENTRY("if-none-match", "", 41); +STATIC_TABLE_ENTRY("if-range", "", 42); +STATIC_TABLE_ENTRY("if-unmodified-since", "", 43); +STATIC_TABLE_ENTRY("last-modified", "", 44); +STATIC_TABLE_ENTRY("link", "", 45); +STATIC_TABLE_ENTRY("location", "", 46); +STATIC_TABLE_ENTRY("max-forwards", "", 47); +STATIC_TABLE_ENTRY("proxy-authenticate", "", 48); +STATIC_TABLE_ENTRY("proxy-authorization", "", 49); +STATIC_TABLE_ENTRY("range", "", 50); +STATIC_TABLE_ENTRY("referer", "", 51); +STATIC_TABLE_ENTRY("refresh", "", 52); +STATIC_TABLE_ENTRY("retry-after", "", 53); +STATIC_TABLE_ENTRY("server", "", 54); +STATIC_TABLE_ENTRY("set-cookie", "", 55); +STATIC_TABLE_ENTRY("strict-transport-security", "", 56); +STATIC_TABLE_ENTRY("transfer-encoding", "", 57); +STATIC_TABLE_ENTRY("user-agent", "", 58); +STATIC_TABLE_ENTRY("vary", "", 59); +STATIC_TABLE_ENTRY("via", "", 60); +STATIC_TABLE_ENTRY("www-authenticate", "", 61); diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/http2_hpack_constants.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/http2_hpack_constants.cc new file mode 100644 index 00000000000..e4a71b8fe9f --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/http2_hpack_constants.cc @@ -0,0 +1,31 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/http2/hpack/http2_hpack_constants.h" + +#include "absl/strings/str_cat.h" + +namespace http2 { + +std::string HpackEntryTypeToString(HpackEntryType v) { + switch (v) { + case HpackEntryType::kIndexedHeader: + return "kIndexedHeader"; + case HpackEntryType::kDynamicTableSizeUpdate: + return "kDynamicTableSizeUpdate"; + case HpackEntryType::kIndexedLiteralHeader: + return "kIndexedLiteralHeader"; + case HpackEntryType::kUnindexedLiteralHeader: + return "kUnindexedLiteralHeader"; + case HpackEntryType::kNeverIndexedLiteralHeader: + return "kNeverIndexedLiteralHeader"; + } + return absl::StrCat("UnknownHpackEntryType(", static_cast<int>(v), ")"); +} + +std::ostream& operator<<(std::ostream& out, HpackEntryType v) { + return out << HpackEntryTypeToString(v); +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/http2_hpack_constants.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/http2_hpack_constants.h new file mode 100644 index 00000000000..a10b140bb70 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/http2_hpack_constants.h @@ -0,0 +1,63 @@ +// 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_HPACK_HTTP2_HPACK_CONSTANTS_H_ +#define QUICHE_HTTP2_HPACK_HTTP2_HPACK_CONSTANTS_H_ + +// Enum HpackEntryType identifies the 5 basic types of HPACK Block Entries. +// +// See the spec for details: +// https://http2.github.io/http2-spec/compression.html#rfc.section.6 + +#include <ostream> +#include <string> + +#include "quiche/common/platform/api/quiche_export.h" + +namespace http2 { + +const size_t kFirstDynamicTableIndex = 62; + +enum class HpackEntryType { + // Entry is an index into the static or dynamic table. Decoding it has no + // effect on the dynamic table. + kIndexedHeader, + + // The entry contains a literal value. The name may be either a literal or a + // reference to an entry in the static or dynamic table. + // The entry is added to the dynamic table after decoding. + kIndexedLiteralHeader, + + // The entry contains a literal value. The name may be either a literal or a + // reference to an entry in the static or dynamic table. + // The entry is not added to the dynamic table after decoding, but a proxy + // may choose to insert the entry into its dynamic table when forwarding + // to another endpoint. + kUnindexedLiteralHeader, + + // The entry contains a literal value. The name may be either a literal or a + // reference to an entry in the static or dynamic table. + // The entry is not added to the dynamic table after decoding, and a proxy + // must NOT insert the entry into its dynamic table when forwarding to another + // endpoint. + kNeverIndexedLiteralHeader, + + // Entry conveys the size limit of the dynamic table of the encoder to + // the decoder. May be used to flush the table by sending a zero and then + // resetting the size back up to the maximum that the encoder will use + // (within the limits of SETTINGS_HEADER_TABLE_SIZE sent by the + // decoder to the encoder, with the default of 4096 assumed). + kDynamicTableSizeUpdate, +}; + +// Returns the name of the enum member. +QUICHE_EXPORT_PRIVATE std::string HpackEntryTypeToString(HpackEntryType v); + +// Inserts the name of the enum member into |out|. +QUICHE_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + HpackEntryType v); + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_HTTP2_HPACK_CONSTANTS_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/http2_hpack_constants_test.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/http2_hpack_constants_test.cc new file mode 100644 index 00000000000..61b465f2fe4 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/http2_hpack_constants_test.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/hpack/http2_hpack_constants.h" + +#include <sstream> + +#include "quiche/http2/platform/api/http2_logging.h" +#include "quiche/common/platform/api/quiche_test.h" + +namespace http2 { +namespace test { +namespace { + +TEST(HpackEntryTypeTest, HpackEntryTypeToString) { + EXPECT_EQ("kIndexedHeader", + HpackEntryTypeToString(HpackEntryType::kIndexedHeader)); + EXPECT_EQ("kDynamicTableSizeUpdate", + HpackEntryTypeToString(HpackEntryType::kDynamicTableSizeUpdate)); + EXPECT_EQ("kIndexedLiteralHeader", + HpackEntryTypeToString(HpackEntryType::kIndexedLiteralHeader)); + EXPECT_EQ("kUnindexedLiteralHeader", + HpackEntryTypeToString(HpackEntryType::kUnindexedLiteralHeader)); + EXPECT_EQ("kNeverIndexedLiteralHeader", + HpackEntryTypeToString(HpackEntryType::kNeverIndexedLiteralHeader)); + EXPECT_EQ("UnknownHpackEntryType(12321)", + HpackEntryTypeToString(static_cast<HpackEntryType>(12321))); +} + +TEST(HpackEntryTypeTest, OutputHpackEntryType) { + { + std::stringstream log; + log << HpackEntryType::kIndexedHeader; + EXPECT_EQ("kIndexedHeader", log.str()); + } + { + std::stringstream log; + log << HpackEntryType::kDynamicTableSizeUpdate; + EXPECT_EQ("kDynamicTableSizeUpdate", log.str()); + } + { + std::stringstream log; + log << HpackEntryType::kIndexedLiteralHeader; + EXPECT_EQ("kIndexedLiteralHeader", log.str()); + } + { + std::stringstream log; + log << HpackEntryType::kUnindexedLiteralHeader; + EXPECT_EQ("kUnindexedLiteralHeader", log.str()); + } + { + std::stringstream log; + log << HpackEntryType::kNeverIndexedLiteralHeader; + EXPECT_EQ("kNeverIndexedLiteralHeader", log.str()); + } + { + std::stringstream log; + log << static_cast<HpackEntryType>(1234321); + EXPECT_EQ("UnknownHpackEntryType(1234321)", log.str()); + } +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_decoder.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_decoder.cc new file mode 100644 index 00000000000..6a026a4b08a --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_decoder.cc @@ -0,0 +1,485 @@ +// 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/hpack/huffman/hpack_huffman_decoder.h" + +#include <bitset> +#include <limits> + +#include "quiche/http2/platform/api/http2_logging.h" + +// Terminology: +// +// Symbol - a plain text (unencoded) character (uint8), or the End-of-String +// (EOS) symbol, 256. +// +// Code - the sequence of bits used to encode a symbol, varying in length from +// 5 bits for the most common symbols (e.g. '0', '1', and 'a'), to +// 30 bits for the least common (e.g. the EOS symbol). +// For those symbols whose codes have the same length, their code values +// are sorted such that the lower symbol value has a lower code value. +// +// Canonical - a symbol's cardinal value when sorted first by code length, and +// then by symbol value. For example, canonical 0 is for ASCII '0' +// (uint8 value 0x30), which is the first of the symbols whose code +// is 5 bits long, and the last canonical is EOS, which is the last +// of the symbols whose code is 30 bits long. + +namespace http2 { +namespace { + +// HuffmanCode is used to store the codes associated with symbols (a pattern of +// from 5 to 30 bits). +typedef uint32_t HuffmanCode; + +// HuffmanCodeBitCount is used to store a count of bits in a code. +typedef uint16_t HuffmanCodeBitCount; + +// HuffmanCodeBitSet is used for producing a string version of a code because +// std::bitset logs nicely. +typedef std::bitset<32> HuffmanCodeBitSet; +typedef std::bitset<64> HuffmanAccumulatorBitSet; + +static constexpr HuffmanCodeBitCount kMinCodeBitCount = 5; +static constexpr HuffmanCodeBitCount kMaxCodeBitCount = 30; +static constexpr HuffmanCodeBitCount kHuffmanCodeBitCount = + std::numeric_limits<HuffmanCode>::digits; + +static_assert(std::numeric_limits<HuffmanCode>::digits >= kMaxCodeBitCount, + "HuffmanCode isn't big enough."); + +static_assert(std::numeric_limits<HuffmanAccumulator>::digits >= + kMaxCodeBitCount, + "HuffmanAccumulator isn't big enough."); + +static constexpr HuffmanAccumulatorBitCount kHuffmanAccumulatorBitCount = + std::numeric_limits<HuffmanAccumulator>::digits; +static constexpr HuffmanAccumulatorBitCount kExtraAccumulatorBitCount = + kHuffmanAccumulatorBitCount - kHuffmanCodeBitCount; + +// PrefixInfo holds info about a group of codes that are all of the same length. +struct PrefixInfo { + // Given the leading bits (32 in this case) of the encoded string, and that + // they start with a code of length |code_length|, return the corresponding + // canonical for that leading code. + uint32_t DecodeToCanonical(HuffmanCode bits) const { + // What is the position of the canonical symbol being decoded within + // the canonical symbols of |length|? + HuffmanCode ordinal_in_length = + ((bits - first_code) >> (kHuffmanCodeBitCount - code_length)); + + // Combined with |canonical| to produce the position of the canonical symbol + // being decoded within all of the canonical symbols. + return first_canonical + ordinal_in_length; + } + + const HuffmanCode first_code; // First code of this length, left justified in + // the field (i.e. the first bit of the code is + // the high-order bit). + const uint16_t code_length; // Length of the prefix code |base|. + const uint16_t first_canonical; // First canonical symbol of this length. +}; + +inline std::ostream& operator<<(std::ostream& out, const PrefixInfo& v) { + return out << "{first_code: " << HuffmanCodeBitSet(v.first_code) + << ", code_length: " << v.code_length + << ", first_canonical: " << v.first_canonical << "}"; +} + +// Given |value|, a sequence of the leading bits remaining to be decoded, +// figure out which group of canonicals (by code length) that value starts +// with. This function was generated. +PrefixInfo PrefixToInfo(HuffmanCode value) { + if (value < 0b10111000000000000000000000000000) { + if (value < 0b01010000000000000000000000000000) { + return {0b00000000000000000000000000000000, 5, 0}; + } else { + return {0b01010000000000000000000000000000, 6, 10}; + } + } else { + if (value < 0b11111110000000000000000000000000) { + if (value < 0b11111000000000000000000000000000) { + return {0b10111000000000000000000000000000, 7, 36}; + } else { + return {0b11111000000000000000000000000000, 8, 68}; + } + } else { + if (value < 0b11111111110000000000000000000000) { + if (value < 0b11111111101000000000000000000000) { + if (value < 0b11111111010000000000000000000000) { + return {0b11111110000000000000000000000000, 10, 74}; + } else { + return {0b11111111010000000000000000000000, 11, 79}; + } + } else { + return {0b11111111101000000000000000000000, 12, 82}; + } + } else { + if (value < 0b11111111111111100000000000000000) { + if (value < 0b11111111111110000000000000000000) { + if (value < 0b11111111111100000000000000000000) { + return {0b11111111110000000000000000000000, 13, 84}; + } else { + return {0b11111111111100000000000000000000, 14, 90}; + } + } else { + return {0b11111111111110000000000000000000, 15, 92}; + } + } else { + if (value < 0b11111111111111110100100000000000) { + if (value < 0b11111111111111101110000000000000) { + if (value < 0b11111111111111100110000000000000) { + return {0b11111111111111100000000000000000, 19, 95}; + } else { + return {0b11111111111111100110000000000000, 20, 98}; + } + } else { + return {0b11111111111111101110000000000000, 21, 106}; + } + } else { + if (value < 0b11111111111111111110101000000000) { + if (value < 0b11111111111111111011000000000000) { + return {0b11111111111111110100100000000000, 22, 119}; + } else { + return {0b11111111111111111011000000000000, 23, 145}; + } + } else { + if (value < 0b11111111111111111111101111000000) { + if (value < 0b11111111111111111111100000000000) { + if (value < 0b11111111111111111111011000000000) { + return {0b11111111111111111110101000000000, 24, 174}; + } else { + return {0b11111111111111111111011000000000, 25, 186}; + } + } else { + return {0b11111111111111111111100000000000, 26, 190}; + } + } else { + if (value < 0b11111111111111111111111111110000) { + if (value < 0b11111111111111111111111000100000) { + return {0b11111111111111111111101111000000, 27, 205}; + } else { + return {0b11111111111111111111111000100000, 28, 224}; + } + } else { + return {0b11111111111111111111111111110000, 30, 253}; + } + } + } + } + } + } + } + } +} + +// Mapping from canonical symbol (0 to 255) to actual symbol. +// clang-format off +constexpr unsigned char kCanonicalToSymbol[] = { + '0', '1', '2', 'a', 'c', 'e', 'i', 'o', + 's', 't', 0x20, '%', '-', '.', '/', '3', + '4', '5', '6', '7', '8', '9', '=', 'A', + '_', 'b', 'd', 'f', 'g', 'h', 'l', 'm', + 'n', 'p', 'r', 'u', ':', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'Y', 'j', 'k', 'q', 'v', + 'w', 'x', 'y', 'z', '&', '*', ',', ';', + 'X', 'Z', '!', '\"', '(', ')', '?', '\'', + '+', '|', '#', '>', 0x00, '$', '@', '[', + ']', '~', '^', '}', '<', '`', '{', '\\', + 0xc3, 0xd0, 0x80, 0x82, 0x83, 0xa2, 0xb8, 0xc2, + 0xe0, 0xe2, 0x99, 0xa1, 0xa7, 0xac, 0xb0, 0xb1, + 0xb3, 0xd1, 0xd8, 0xd9, 0xe3, 0xe5, 0xe6, 0x81, + 0x84, 0x85, 0x86, 0x88, 0x92, 0x9a, 0x9c, 0xa0, + 0xa3, 0xa4, 0xa9, 0xaa, 0xad, 0xb2, 0xb5, 0xb9, + 0xba, 0xbb, 0xbd, 0xbe, 0xc4, 0xc6, 0xe4, 0xe8, + 0xe9, 0x01, 0x87, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, + 0x8f, 0x93, 0x95, 0x96, 0x97, 0x98, 0x9b, 0x9d, + 0x9e, 0xa5, 0xa6, 0xa8, 0xae, 0xaf, 0xb4, 0xb6, + 0xb7, 0xbc, 0xbf, 0xc5, 0xe7, 0xef, 0x09, 0x8e, + 0x90, 0x91, 0x94, 0x9f, 0xab, 0xce, 0xd7, 0xe1, + 0xec, 0xed, 0xc7, 0xcf, 0xea, 0xeb, 0xc0, 0xc1, + 0xc8, 0xc9, 0xca, 0xcd, 0xd2, 0xd5, 0xda, 0xdb, + 0xee, 0xf0, 0xf2, 0xf3, 0xff, 0xcb, 0xcc, 0xd3, + 0xd4, 0xd6, 0xdd, 0xde, 0xdf, 0xf1, 0xf4, 0xf5, + 0xf6, 0xf7, 0xf8, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0b, + 0x0c, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, 0x7f, 0xdc, 0xf9, 0x0a, 0x0d, 0x16, +}; +// clang-format on + +constexpr size_t kShortCodeTableSize = 124; +struct ShortCodeInfo { + uint8_t symbol; + uint8_t length; +} kShortCodeTable[kShortCodeTableSize] = { + {0x30, 5}, // Match: 0b0000000, Symbol: 0 + {0x30, 5}, // Match: 0b0000001, Symbol: 0 + {0x30, 5}, // Match: 0b0000010, Symbol: 0 + {0x30, 5}, // Match: 0b0000011, Symbol: 0 + {0x31, 5}, // Match: 0b0000100, Symbol: 1 + {0x31, 5}, // Match: 0b0000101, Symbol: 1 + {0x31, 5}, // Match: 0b0000110, Symbol: 1 + {0x31, 5}, // Match: 0b0000111, Symbol: 1 + {0x32, 5}, // Match: 0b0001000, Symbol: 2 + {0x32, 5}, // Match: 0b0001001, Symbol: 2 + {0x32, 5}, // Match: 0b0001010, Symbol: 2 + {0x32, 5}, // Match: 0b0001011, Symbol: 2 + {0x61, 5}, // Match: 0b0001100, Symbol: a + {0x61, 5}, // Match: 0b0001101, Symbol: a + {0x61, 5}, // Match: 0b0001110, Symbol: a + {0x61, 5}, // Match: 0b0001111, Symbol: a + {0x63, 5}, // Match: 0b0010000, Symbol: c + {0x63, 5}, // Match: 0b0010001, Symbol: c + {0x63, 5}, // Match: 0b0010010, Symbol: c + {0x63, 5}, // Match: 0b0010011, Symbol: c + {0x65, 5}, // Match: 0b0010100, Symbol: e + {0x65, 5}, // Match: 0b0010101, Symbol: e + {0x65, 5}, // Match: 0b0010110, Symbol: e + {0x65, 5}, // Match: 0b0010111, Symbol: e + {0x69, 5}, // Match: 0b0011000, Symbol: i + {0x69, 5}, // Match: 0b0011001, Symbol: i + {0x69, 5}, // Match: 0b0011010, Symbol: i + {0x69, 5}, // Match: 0b0011011, Symbol: i + {0x6f, 5}, // Match: 0b0011100, Symbol: o + {0x6f, 5}, // Match: 0b0011101, Symbol: o + {0x6f, 5}, // Match: 0b0011110, Symbol: o + {0x6f, 5}, // Match: 0b0011111, Symbol: o + {0x73, 5}, // Match: 0b0100000, Symbol: s + {0x73, 5}, // Match: 0b0100001, Symbol: s + {0x73, 5}, // Match: 0b0100010, Symbol: s + {0x73, 5}, // Match: 0b0100011, Symbol: s + {0x74, 5}, // Match: 0b0100100, Symbol: t + {0x74, 5}, // Match: 0b0100101, Symbol: t + {0x74, 5}, // Match: 0b0100110, Symbol: t + {0x74, 5}, // Match: 0b0100111, Symbol: t + {0x20, 6}, // Match: 0b0101000, Symbol: (space) + {0x20, 6}, // Match: 0b0101001, Symbol: (space) + {0x25, 6}, // Match: 0b0101010, Symbol: % + {0x25, 6}, // Match: 0b0101011, Symbol: % + {0x2d, 6}, // Match: 0b0101100, Symbol: - + {0x2d, 6}, // Match: 0b0101101, Symbol: - + {0x2e, 6}, // Match: 0b0101110, Symbol: . + {0x2e, 6}, // Match: 0b0101111, Symbol: . + {0x2f, 6}, // Match: 0b0110000, Symbol: / + {0x2f, 6}, // Match: 0b0110001, Symbol: / + {0x33, 6}, // Match: 0b0110010, Symbol: 3 + {0x33, 6}, // Match: 0b0110011, Symbol: 3 + {0x34, 6}, // Match: 0b0110100, Symbol: 4 + {0x34, 6}, // Match: 0b0110101, Symbol: 4 + {0x35, 6}, // Match: 0b0110110, Symbol: 5 + {0x35, 6}, // Match: 0b0110111, Symbol: 5 + {0x36, 6}, // Match: 0b0111000, Symbol: 6 + {0x36, 6}, // Match: 0b0111001, Symbol: 6 + {0x37, 6}, // Match: 0b0111010, Symbol: 7 + {0x37, 6}, // Match: 0b0111011, Symbol: 7 + {0x38, 6}, // Match: 0b0111100, Symbol: 8 + {0x38, 6}, // Match: 0b0111101, Symbol: 8 + {0x39, 6}, // Match: 0b0111110, Symbol: 9 + {0x39, 6}, // Match: 0b0111111, Symbol: 9 + {0x3d, 6}, // Match: 0b1000000, Symbol: = + {0x3d, 6}, // Match: 0b1000001, Symbol: = + {0x41, 6}, // Match: 0b1000010, Symbol: A + {0x41, 6}, // Match: 0b1000011, Symbol: A + {0x5f, 6}, // Match: 0b1000100, Symbol: _ + {0x5f, 6}, // Match: 0b1000101, Symbol: _ + {0x62, 6}, // Match: 0b1000110, Symbol: b + {0x62, 6}, // Match: 0b1000111, Symbol: b + {0x64, 6}, // Match: 0b1001000, Symbol: d + {0x64, 6}, // Match: 0b1001001, Symbol: d + {0x66, 6}, // Match: 0b1001010, Symbol: f + {0x66, 6}, // Match: 0b1001011, Symbol: f + {0x67, 6}, // Match: 0b1001100, Symbol: g + {0x67, 6}, // Match: 0b1001101, Symbol: g + {0x68, 6}, // Match: 0b1001110, Symbol: h + {0x68, 6}, // Match: 0b1001111, Symbol: h + {0x6c, 6}, // Match: 0b1010000, Symbol: l + {0x6c, 6}, // Match: 0b1010001, Symbol: l + {0x6d, 6}, // Match: 0b1010010, Symbol: m + {0x6d, 6}, // Match: 0b1010011, Symbol: m + {0x6e, 6}, // Match: 0b1010100, Symbol: n + {0x6e, 6}, // Match: 0b1010101, Symbol: n + {0x70, 6}, // Match: 0b1010110, Symbol: p + {0x70, 6}, // Match: 0b1010111, Symbol: p + {0x72, 6}, // Match: 0b1011000, Symbol: r + {0x72, 6}, // Match: 0b1011001, Symbol: r + {0x75, 6}, // Match: 0b1011010, Symbol: u + {0x75, 6}, // Match: 0b1011011, Symbol: u + {0x3a, 7}, // Match: 0b1011100, Symbol: : + {0x42, 7}, // Match: 0b1011101, Symbol: B + {0x43, 7}, // Match: 0b1011110, Symbol: C + {0x44, 7}, // Match: 0b1011111, Symbol: D + {0x45, 7}, // Match: 0b1100000, Symbol: E + {0x46, 7}, // Match: 0b1100001, Symbol: F + {0x47, 7}, // Match: 0b1100010, Symbol: G + {0x48, 7}, // Match: 0b1100011, Symbol: H + {0x49, 7}, // Match: 0b1100100, Symbol: I + {0x4a, 7}, // Match: 0b1100101, Symbol: J + {0x4b, 7}, // Match: 0b1100110, Symbol: K + {0x4c, 7}, // Match: 0b1100111, Symbol: L + {0x4d, 7}, // Match: 0b1101000, Symbol: M + {0x4e, 7}, // Match: 0b1101001, Symbol: N + {0x4f, 7}, // Match: 0b1101010, Symbol: O + {0x50, 7}, // Match: 0b1101011, Symbol: P + {0x51, 7}, // Match: 0b1101100, Symbol: Q + {0x52, 7}, // Match: 0b1101101, Symbol: R + {0x53, 7}, // Match: 0b1101110, Symbol: S + {0x54, 7}, // Match: 0b1101111, Symbol: T + {0x55, 7}, // Match: 0b1110000, Symbol: U + {0x56, 7}, // Match: 0b1110001, Symbol: V + {0x57, 7}, // Match: 0b1110010, Symbol: W + {0x59, 7}, // Match: 0b1110011, Symbol: Y + {0x6a, 7}, // Match: 0b1110100, Symbol: j + {0x6b, 7}, // Match: 0b1110101, Symbol: k + {0x71, 7}, // Match: 0b1110110, Symbol: q + {0x76, 7}, // Match: 0b1110111, Symbol: v + {0x77, 7}, // Match: 0b1111000, Symbol: w + {0x78, 7}, // Match: 0b1111001, Symbol: x + {0x79, 7}, // Match: 0b1111010, Symbol: y + {0x7a, 7}, // Match: 0b1111011, Symbol: z +}; + +} // namespace + +HuffmanBitBuffer::HuffmanBitBuffer() { + Reset(); +} + +void HuffmanBitBuffer::Reset() { + accumulator_ = 0; + count_ = 0; +} + +size_t HuffmanBitBuffer::AppendBytes(absl::string_view input) { + HuffmanAccumulatorBitCount free_cnt = free_count(); + size_t bytes_available = input.size(); + if (free_cnt < 8 || bytes_available == 0) { + return 0; + } + + // Top up |accumulator_| until there isn't room for a whole byte. + size_t bytes_used = 0; + auto* ptr = reinterpret_cast<const uint8_t*>(input.data()); + do { + auto b = static_cast<HuffmanAccumulator>(*ptr++); + free_cnt -= 8; + accumulator_ |= (b << free_cnt); + ++bytes_used; + } while (free_cnt >= 8 && bytes_used < bytes_available); + count_ += (bytes_used * 8); + return bytes_used; +} + +HuffmanAccumulatorBitCount HuffmanBitBuffer::free_count() const { + return kHuffmanAccumulatorBitCount - count_; +} + +void HuffmanBitBuffer::ConsumeBits(HuffmanAccumulatorBitCount code_length) { + QUICHE_DCHECK_LE(code_length, count_); + accumulator_ <<= code_length; + count_ -= code_length; +} + +bool HuffmanBitBuffer::InputProperlyTerminated() const { + auto cnt = count(); + if (cnt < 8) { + if (cnt == 0) { + return true; + } + HuffmanAccumulator expected = ~(~HuffmanAccumulator() >> cnt); + // We expect all the bits below the high order |cnt| bits of accumulator_ + // to be cleared as we perform left shift operations while decoding. + QUICHE_DCHECK_EQ(accumulator_ & ~expected, 0u) + << "\n expected: " << HuffmanAccumulatorBitSet(expected) << "\n " + << *this; + return accumulator_ == expected; + } + return false; +} + +std::string HuffmanBitBuffer::DebugString() const { + std::stringstream ss; + ss << "{accumulator: " << HuffmanAccumulatorBitSet(accumulator_) + << "; count: " << count_ << "}"; + return ss.str(); +} + +HpackHuffmanDecoder::HpackHuffmanDecoder() = default; + +HpackHuffmanDecoder::~HpackHuffmanDecoder() = default; + +bool HpackHuffmanDecoder::Decode(absl::string_view input, std::string* output) { + HTTP2_DVLOG(1) << "HpackHuffmanDecoder::Decode"; + + // Fill bit_buffer_ from input. + input.remove_prefix(bit_buffer_.AppendBytes(input)); + + while (true) { + HTTP2_DVLOG(3) << "Enter Decode Loop, bit_buffer_: " << bit_buffer_; + if (bit_buffer_.count() >= 7) { + // Get high 7 bits of the bit buffer, see if that contains a complete + // code of 5, 6 or 7 bits. + uint8_t short_code = + bit_buffer_.value() >> (kHuffmanAccumulatorBitCount - 7); + QUICHE_DCHECK_LT(short_code, 128); + if (short_code < kShortCodeTableSize) { + ShortCodeInfo info = kShortCodeTable[short_code]; + bit_buffer_.ConsumeBits(info.length); + output->push_back(static_cast<char>(info.symbol)); + continue; + } + // The code is more than 7 bits long. Use PrefixToInfo, etc. to decode + // longer codes. + } else { + // We may have (mostly) drained bit_buffer_. If we can top it up, try + // using the table decoder above. + size_t byte_count = bit_buffer_.AppendBytes(input); + if (byte_count > 0) { + input.remove_prefix(byte_count); + continue; + } + } + + HuffmanCode code_prefix = bit_buffer_.value() >> kExtraAccumulatorBitCount; + HTTP2_DVLOG(3) << "code_prefix: " << HuffmanCodeBitSet(code_prefix); + + PrefixInfo prefix_info = PrefixToInfo(code_prefix); + HTTP2_DVLOG(3) << "prefix_info: " << prefix_info; + QUICHE_DCHECK_LE(kMinCodeBitCount, prefix_info.code_length); + QUICHE_DCHECK_LE(prefix_info.code_length, kMaxCodeBitCount); + + if (prefix_info.code_length <= bit_buffer_.count()) { + // We have enough bits for one code. + uint32_t canonical = prefix_info.DecodeToCanonical(code_prefix); + if (canonical < 256) { + // Valid code. + char c = kCanonicalToSymbol[canonical]; + output->push_back(c); + bit_buffer_.ConsumeBits(prefix_info.code_length); + continue; + } + // Encoder is not supposed to explicity encode the EOS symbol. + HTTP2_DLOG(ERROR) << "EOS explicitly encoded!\n " << bit_buffer_ << "\n " + << prefix_info; + return false; + } + // bit_buffer_ doesn't have enough bits in it to decode the next symbol. + // Append to it as many bytes as are available AND fit. + size_t byte_count = bit_buffer_.AppendBytes(input); + if (byte_count == 0) { + QUICHE_DCHECK_EQ(input.size(), 0u); + return true; + } + input.remove_prefix(byte_count); + } +} + +std::string HpackHuffmanDecoder::DebugString() const { + return bit_buffer_.DebugString(); +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_decoder.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_decoder.h new file mode 100644 index 00000000000..9befd6c628d --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_decoder.h @@ -0,0 +1,134 @@ +// 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_HPACK_HUFFMAN_HPACK_HUFFMAN_DECODER_H_ +#define QUICHE_HTTP2_HPACK_HUFFMAN_HPACK_HUFFMAN_DECODER_H_ + +// HpackHuffmanDecoder is an incremental decoder of strings that have been +// encoded using the Huffman table defined in the HPACK spec. +// By incremental, we mean that the HpackHuffmanDecoder::Decode method does +// not require the entire string to be provided, and can instead decode the +// string as fragments of it become available (e.g. as HPACK block fragments +// are received for decoding by HpackEntryDecoder). + +#include <stddef.h> + +#include <cstdint> +#include <iosfwd> +#include <string> + +#include "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_export.h" + +namespace http2 { + +// HuffmanAccumulator is used to store bits during decoding, e.g. next N bits +// that have not yet been decoded, but have been extracted from the encoded +// string). An advantage of using a uint64 for the accumulator +// is that it has room for the bits of the longest code plus the bits of a full +// byte; that means that when adding more bits to the accumulator, it can always +// be done in whole bytes. For example, if we currently have 26 bits in the +// accumulator, and need more to decode the current symbol, we can add a whole +// byte to the accumulator, and not have to do juggling with adding 6 bits (to +// reach 30), and then keep track of the last two bits we've not been able to +// add to the accumulator. +typedef uint64_t HuffmanAccumulator; +typedef size_t HuffmanAccumulatorBitCount; + +// HuffmanBitBuffer stores the leading edge of bits to be decoded. The high +// order bit of accumulator_ is the next bit to be decoded. +class QUICHE_EXPORT_PRIVATE HuffmanBitBuffer { + public: + HuffmanBitBuffer(); + + // Prepare for decoding a new Huffman encoded string. + void Reset(); + + // Add as many whole bytes to the accumulator (accumulator_) as possible, + // returning the number of bytes added. + size_t AppendBytes(absl::string_view input); + + // Get the bits of the accumulator. + HuffmanAccumulator value() const { return accumulator_; } + + // Number of bits of the encoded string that are in the accumulator + // (accumulator_). + HuffmanAccumulatorBitCount count() const { return count_; } + + // Are there no bits in the accumulator? + bool IsEmpty() const { return count_ == 0; } + + // Number of additional bits that can be added to the accumulator. + HuffmanAccumulatorBitCount free_count() const; + + // Consume the leading |code_length| bits of the accumulator. + void ConsumeBits(HuffmanAccumulatorBitCount code_length); + + // Are the contents valid for the end of a Huffman encoded string? The RFC + // states that EOS (end-of-string) symbol must not be explicitly encoded in + // the bit stream, but any unused bits in the final byte must be set to the + // prefix of the EOS symbol, which is all 1 bits. So there can be at most 7 + // such bits. + // Returns true if the bit buffer is empty, or contains at most 7 bits, all + // of them 1. Otherwise returns false. + bool InputProperlyTerminated() const; + + std::string DebugString() const; + + private: + HuffmanAccumulator accumulator_; + HuffmanAccumulatorBitCount count_; +}; + +inline std::ostream& operator<<(std::ostream& out, const HuffmanBitBuffer& v) { + return out << v.DebugString(); +} + +class QUICHE_EXPORT_PRIVATE HpackHuffmanDecoder { + public: + HpackHuffmanDecoder(); + ~HpackHuffmanDecoder(); + + // Prepare for decoding a new Huffman encoded string. + void Reset() { bit_buffer_.Reset(); } + + // Decode the portion of a HPACK Huffman encoded string that is in |input|, + // appending the decoded symbols into |*output|, stopping when more bits are + // needed to determine the next symbol, which/ means that the input has been + // drained, and also that the bit_buffer_ is empty or that the bits that are + // in it are not a whole symbol. + // If |input| is the start of a string, the caller must first call Reset. + // If |input| includes the end of the encoded string, the caller must call + // InputProperlyTerminated after Decode has returned true in order to + // determine if the encoded string was properly terminated. + // Returns false if something went wrong (e.g. the encoding contains the code + // EOS symbol). Otherwise returns true, in which case input has been fully + // decoded or buffered; in particular, if the low-order bit of the final byte + // of the input is not the last bit of an encoded symbol, then bit_buffer_ + // will contain the leading bits of the code for that symbol, but not the + // final bits of that code. + // Note that output should be empty, but that it is not cleared by Decode(). + bool Decode(absl::string_view input, std::string* output); + + // Is what remains in the bit_buffer_ valid at the end of an encoded string? + // Call after passing the the final portion of a Huffman string to Decode, + // and getting true as the result. + bool InputProperlyTerminated() const { + return bit_buffer_.InputProperlyTerminated(); + } + + std::string DebugString() const; + + private: + HuffmanBitBuffer bit_buffer_; +}; + +inline std::ostream& operator<<(std::ostream& out, + const HpackHuffmanDecoder& v) { + return out << v.DebugString(); +} + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_HUFFMAN_HPACK_HUFFMAN_DECODER_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_decoder_test.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_decoder_test.cc new file mode 100644 index 00000000000..728d3d47b21 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_decoder_test.cc @@ -0,0 +1,242 @@ +// 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/hpack/huffman/hpack_huffman_decoder.h" + +// Tests of HpackHuffmanDecoder and HuffmanBitBuffer. + +#include <iostream> + +#include "absl/base/macros.h" +#include "absl/strings/escaping.h" +#include "quiche/http2/decoder/decode_buffer.h" +#include "quiche/http2/decoder/decode_status.h" +#include "quiche/http2/tools/random_decoder_test.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/common/platform/api/quiche_test_helpers.h" + +namespace http2 { +namespace test { +namespace { + +TEST(HuffmanBitBufferTest, Reset) { + HuffmanBitBuffer bb; + EXPECT_TRUE(bb.IsEmpty()); + EXPECT_TRUE(bb.InputProperlyTerminated()); + EXPECT_EQ(bb.count(), 0u); + EXPECT_EQ(bb.free_count(), 64u); + EXPECT_EQ(bb.value(), 0u); +} + +TEST(HuffmanBitBufferTest, AppendBytesAligned) { + std::string s; + s.push_back('\x11'); + s.push_back('\x22'); + s.push_back('\x33'); + absl::string_view sp(s); + + HuffmanBitBuffer bb; + sp.remove_prefix(bb.AppendBytes(sp)); + EXPECT_TRUE(sp.empty()); + EXPECT_FALSE(bb.IsEmpty()) << bb; + EXPECT_FALSE(bb.InputProperlyTerminated()); + EXPECT_EQ(bb.count(), 24u) << bb; + EXPECT_EQ(bb.free_count(), 40u) << bb; + EXPECT_EQ(bb.value(), HuffmanAccumulator(0x112233) << 40) << bb; + + s.clear(); + s.push_back('\x44'); + sp = s; + + sp.remove_prefix(bb.AppendBytes(sp)); + EXPECT_TRUE(sp.empty()); + EXPECT_EQ(bb.count(), 32u) << bb; + EXPECT_EQ(bb.free_count(), 32u) << bb; + EXPECT_EQ(bb.value(), HuffmanAccumulator(0x11223344) << 32) << bb; + + s.clear(); + s.push_back('\x55'); + s.push_back('\x66'); + s.push_back('\x77'); + s.push_back('\x88'); + s.push_back('\x99'); + sp = s; + + sp.remove_prefix(bb.AppendBytes(sp)); + EXPECT_EQ(sp.size(), 1u); + EXPECT_EQ('\x99', sp[0]); + EXPECT_EQ(bb.count(), 64u) << bb; + EXPECT_EQ(bb.free_count(), 0u) << bb; + EXPECT_EQ(bb.value(), HuffmanAccumulator(0x1122334455667788LL)) << bb; + + sp.remove_prefix(bb.AppendBytes(sp)); + EXPECT_EQ(sp.size(), 1u); + EXPECT_EQ('\x99', sp[0]); + EXPECT_EQ(bb.count(), 64u) << bb; + EXPECT_EQ(bb.free_count(), 0u) << bb; + EXPECT_EQ(bb.value(), HuffmanAccumulator(0x1122334455667788LL)) << bb; +} + +TEST(HuffmanBitBufferTest, ConsumeBits) { + std::string s; + s.push_back('\x11'); + s.push_back('\x22'); + s.push_back('\x33'); + absl::string_view sp(s); + + HuffmanBitBuffer bb; + sp.remove_prefix(bb.AppendBytes(sp)); + EXPECT_TRUE(sp.empty()); + + bb.ConsumeBits(1); + EXPECT_EQ(bb.count(), 23u) << bb; + EXPECT_EQ(bb.free_count(), 41u) << bb; + EXPECT_EQ(bb.value(), HuffmanAccumulator(0x112233) << 41) << bb; + + bb.ConsumeBits(20); + EXPECT_EQ(bb.count(), 3u) << bb; + EXPECT_EQ(bb.free_count(), 61u) << bb; + EXPECT_EQ(bb.value(), HuffmanAccumulator(0x3) << 61) << bb; +} + +TEST(HuffmanBitBufferTest, AppendBytesUnaligned) { + std::string s; + s.push_back('\x11'); + s.push_back('\x22'); + s.push_back('\x33'); + s.push_back('\x44'); + s.push_back('\x55'); + s.push_back('\x66'); + s.push_back('\x77'); + s.push_back('\x88'); + s.push_back('\x99'); + s.push_back('\xaa'); + s.push_back('\xbb'); + s.push_back('\xcc'); + s.push_back('\xdd'); + absl::string_view sp(s); + + HuffmanBitBuffer bb; + sp.remove_prefix(bb.AppendBytes(sp)); + EXPECT_EQ(sp.size(), 5u); + EXPECT_FALSE(bb.InputProperlyTerminated()); + + bb.ConsumeBits(15); + EXPECT_EQ(bb.count(), 49u) << bb; + EXPECT_EQ(bb.free_count(), 15u) << bb; + + HuffmanAccumulator expected(0x1122334455667788); + expected <<= 15; + EXPECT_EQ(bb.value(), expected); + + sp.remove_prefix(bb.AppendBytes(sp)); + EXPECT_EQ(sp.size(), 4u); + EXPECT_EQ(bb.count(), 57u) << bb; + EXPECT_EQ(bb.free_count(), 7u) << bb; + + expected |= (HuffmanAccumulator(0x99) << 7); + EXPECT_EQ(bb.value(), expected) + << bb << std::hex << "\n actual: " << bb.value() + << "\n expected: " << expected; +} + +class HpackHuffmanDecoderTest : public RandomDecoderTest { + protected: + HpackHuffmanDecoderTest() { + // The decoder may return true, and its accumulator may be empty, at + // many boundaries while decoding, and yet the whole string hasn't + // been decoded. + stop_decode_on_done_ = false; + } + + DecodeStatus StartDecoding(DecodeBuffer* b) override { + input_bytes_seen_ = 0; + output_buffer_.clear(); + decoder_.Reset(); + return ResumeDecoding(b); + } + + DecodeStatus ResumeDecoding(DecodeBuffer* b) override { + input_bytes_seen_ += b->Remaining(); + absl::string_view sp(b->cursor(), b->Remaining()); + if (decoder_.Decode(sp, &output_buffer_)) { + b->AdvanceCursor(b->Remaining()); + // Successfully decoded (or buffered) the bytes in absl::string_view. + EXPECT_LE(input_bytes_seen_, input_bytes_expected_); + // Have we reached the end of the encoded string? + if (input_bytes_expected_ == input_bytes_seen_) { + if (decoder_.InputProperlyTerminated()) { + return DecodeStatus::kDecodeDone; + } else { + return DecodeStatus::kDecodeError; + } + } + return DecodeStatus::kDecodeInProgress; + } + return DecodeStatus::kDecodeError; + } + + HpackHuffmanDecoder decoder_; + std::string output_buffer_; + size_t input_bytes_seen_; + size_t input_bytes_expected_; +}; + +TEST_F(HpackHuffmanDecoderTest, SpecRequestExamples) { + HpackHuffmanDecoder decoder; + std::string test_table[] = { + absl::HexStringToBytes("f1e3c2e5f23a6ba0ab90f4ff"), + "www.example.com", + absl::HexStringToBytes("a8eb10649cbf"), + "no-cache", + absl::HexStringToBytes("25a849e95ba97d7f"), + "custom-key", + absl::HexStringToBytes("25a849e95bb8e8b4bf"), + "custom-value", + }; + for (size_t i = 0; i != ABSL_ARRAYSIZE(test_table); i += 2) { + const std::string& huffman_encoded(test_table[i]); + const std::string& plain_string(test_table[i + 1]); + std::string buffer; + decoder.Reset(); + EXPECT_TRUE(decoder.Decode(huffman_encoded, &buffer)) << decoder; + EXPECT_TRUE(decoder.InputProperlyTerminated()) << decoder; + EXPECT_EQ(buffer, plain_string); + } +} + +TEST_F(HpackHuffmanDecoderTest, SpecResponseExamples) { + HpackHuffmanDecoder decoder; + // clang-format off + std::string test_table[] = { + absl::HexStringToBytes("6402"), + "302", + absl::HexStringToBytes("aec3771a4b"), + "private", + absl::HexStringToBytes("d07abe941054d444a8200595040b8166" + "e082a62d1bff"), + "Mon, 21 Oct 2013 20:13:21 GMT", + absl::HexStringToBytes("9d29ad171863c78f0b97c8e9ae82ae43" + "d3"), + "https://www.example.com", + absl::HexStringToBytes("94e7821dd7f2e6c7b335dfdfcd5b3960" + "d5af27087f3672c1ab270fb5291f9587" + "316065c003ed4ee5b1063d5007"), + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", + }; + // clang-format on + for (size_t i = 0; i != ABSL_ARRAYSIZE(test_table); i += 2) { + const std::string& huffman_encoded(test_table[i]); + const std::string& plain_string(test_table[i + 1]); + std::string buffer; + decoder.Reset(); + EXPECT_TRUE(decoder.Decode(huffman_encoded, &buffer)) << decoder; + EXPECT_TRUE(decoder.InputProperlyTerminated()) << decoder; + EXPECT_EQ(buffer, plain_string); + } +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_encoder.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_encoder.cc new file mode 100644 index 00000000000..b5e3404c668 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_encoder.cc @@ -0,0 +1,129 @@ +// Copyright (c) 2018 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/hpack/huffman/hpack_huffman_encoder.h" + +#include "quiche/http2/hpack/huffman/huffman_spec_tables.h" +#include "quiche/http2/platform/api/http2_logging.h" + +namespace http2 { + +size_t HuffmanSize(absl::string_view plain) { + size_t bits = 0; + for (const uint8_t c : plain) { + bits += HuffmanSpecTables::kCodeLengths[c]; + } + return (bits + 7) / 8; +} + +void HuffmanEncode(absl::string_view plain, + size_t encoded_size, + std::string* huffman) { + QUICHE_DCHECK(huffman != nullptr); + huffman->reserve(huffman->size() + encoded_size); + uint64_t bit_buffer = 0; // High-bit is next bit to output. Not clear if that + // is more performant than having the low-bit be the + // last to be output. + size_t bits_unused = 64; // Number of bits available for the next code. + for (uint8_t c : plain) { + size_t code_length = HuffmanSpecTables::kCodeLengths[c]; + if (bits_unused < code_length) { + // There isn't enough room in bit_buffer for the code of c. + // Flush until bits_unused > 56 (i.e. 64 - 8). + do { + char h = static_cast<char>(bit_buffer >> 56); + bit_buffer <<= 8; + bits_unused += 8; + // Perhaps would be more efficient if we populated an array of chars, + // so we don't have to call push_back each time. Reconsider if used + // for production. + huffman->push_back(h); + } while (bits_unused <= 56); + } + uint64_t code = HuffmanSpecTables::kRightCodes[c]; + size_t shift_by = bits_unused - code_length; + bit_buffer |= (code << shift_by); + bits_unused -= code_length; + } + // bit_buffer contains (64-bits_unused) bits that still need to be flushed. + // Output whole bytes until we don't have any whole bytes left. + size_t bits_used = 64 - bits_unused; + while (bits_used >= 8) { + char h = static_cast<char>(bit_buffer >> 56); + bit_buffer <<= 8; + bits_used -= 8; + huffman->push_back(h); + } + if (bits_used > 0) { + // We have less than a byte left to output. The spec calls for padding out + // the final byte with the leading bits of the EOS symbol (30 1-bits). + constexpr uint64_t leading_eos_bits = 0b11111111; + bit_buffer |= (leading_eos_bits << (56 - bits_used)); + char h = static_cast<char>(bit_buffer >> 56); + huffman->push_back(h); + } +} + +void HuffmanEncodeFast(absl::string_view input, + size_t encoded_size, + std::string* output) { + const size_t original_size = output->size(); + const size_t final_size = original_size + encoded_size; + // Reserve an extra four bytes to avoid accessing unallocated memory (even + // though it would only be OR'd with zeros and thus not modified). + output->resize(final_size + 4, 0); + + // Pointer to first appended byte. + char* const first = &*output->begin() + original_size; + size_t bit_counter = 0; + for (uint8_t c : input) { + // Align the Huffman code to byte boundaries as it needs to be written. + // The longest Huffman code is 30 bits long, and it can be shifted by up to + // 7 bits, requiring 37 bits in total. The most significant 25 bits and + // least significant 2 bits of |code| are always zero. + uint64_t code = static_cast<uint64_t>(HuffmanSpecTables::kLeftCodes[c]) + << (8 - (bit_counter % 8)); + // The byte where the first bit of |code| needs to be written. + char* const current = first + (bit_counter / 8); + + bit_counter += HuffmanSpecTables::kCodeLengths[c]; + + *current |= code >> 32; + + // Do not check if this write is zero before executing it, because with + // uniformly random shifts and an ideal random input distribution + // corresponding to the Huffman tree it would only be zero in 29% of the + // cases. + *(current + 1) |= (code >> 24) & 0xff; + + // Continue to next input character if there is nothing else to write. + // (If next byte is zero, then rest must also be zero.) + if ((code & 0xff0000) == 0) { + continue; + } + *(current + 2) |= (code >> 16) & 0xff; + + // Continue to next input character if there is nothing else to write. + // (If next byte is zero, then rest must also be zero.) + if ((code & 0xff00) == 0) { + continue; + } + *(current + 3) |= (code >> 8) & 0xff; + + // Do not check if this write is zero, because the check would probably be + // as expensive as the write. + *(current + 4) |= code & 0xff; + } + + QUICHE_DCHECK_EQ(encoded_size, (bit_counter + 7) / 8); + + // EOF + if (bit_counter % 8 != 0) { + *(first + encoded_size - 1) |= 0xff >> (bit_counter & 7); + } + + output->resize(final_size); +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_encoder.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_encoder.h new file mode 100644 index 00000000000..7e731870705 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_encoder.h @@ -0,0 +1,40 @@ +// 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_HPACK_HUFFMAN_HPACK_HUFFMAN_ENCODER_H_ +#define QUICHE_HTTP2_HPACK_HUFFMAN_HPACK_HUFFMAN_ENCODER_H_ + +// Functions supporting the encoding of strings using the HPACK-defined Huffman +// table. + +#include <cstddef> // For size_t +#include <string> + +#include "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_export.h" + +namespace http2 { + +// Returns the size of the Huffman encoding of |plain|, which may be greater +// than plain.size(). +QUICHE_EXPORT_PRIVATE size_t HuffmanSize(absl::string_view plain); + +// Encode the plain text string |plain| with the Huffman encoding defined in the +// HPACK RFC, 7541. |encoded_size| is used to pre-allocate storage and it +// should be the value returned by HuffmanSize(). Appends the result to +// |*huffman|. +QUICHE_EXPORT_PRIVATE void HuffmanEncode(absl::string_view plain, + size_t encoded_size, + std::string* huffman); + +// Encode |input| with the Huffman encoding defined RFC7541, used in HPACK and +// QPACK. |encoded_size| must be the value returned by HuffmanSize(). +// Appends the result to the end of |*output|. +QUICHE_EXPORT_PRIVATE void HuffmanEncodeFast(absl::string_view input, + size_t encoded_size, + std::string* output); + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_HUFFMAN_HPACK_HUFFMAN_ENCODER_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_encoder_test.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_encoder_test.cc new file mode 100644 index 00000000000..a670b038785 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_encoder_test.cc @@ -0,0 +1,131 @@ +// Copyright (c) 2018 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/hpack/huffman/hpack_huffman_encoder.h" + +#include "absl/base/macros.h" +#include "absl/strings/escaping.h" +#include "quiche/common/platform/api/quiche_test.h" + +namespace http2 { +namespace { + +class HuffmanEncoderTest : public ::testing::TestWithParam<bool> { + protected: + HuffmanEncoderTest() : use_fast_encoder_(GetParam()) {} + virtual ~HuffmanEncoderTest() = default; + + void Encode(absl::string_view input, + size_t encoded_size, + std::string* output) { + use_fast_encoder_ ? HuffmanEncodeFast(input, encoded_size, output) + : HuffmanEncode(input, encoded_size, output); + } + + const bool use_fast_encoder_; +}; + +INSTANTIATE_TEST_SUITE_P(TwoEncoders, HuffmanEncoderTest, ::testing::Bool()); + +TEST_P(HuffmanEncoderTest, Empty) { + std::string empty(""); + size_t encoded_size = HuffmanSize(empty); + EXPECT_EQ(0u, encoded_size); + + std::string buffer; + Encode(empty, encoded_size, &buffer); + EXPECT_EQ("", buffer); +} + +TEST_P(HuffmanEncoderTest, SpecRequestExamples) { + std::string test_table[] = { + absl::HexStringToBytes("f1e3c2e5f23a6ba0ab90f4ff"), + "www.example.com", + absl::HexStringToBytes("a8eb10649cbf"), + "no-cache", + absl::HexStringToBytes("25a849e95ba97d7f"), + "custom-key", + absl::HexStringToBytes("25a849e95bb8e8b4bf"), + "custom-value", + }; + for (size_t i = 0; i != ABSL_ARRAYSIZE(test_table); i += 2) { + const std::string& huffman_encoded(test_table[i]); + const std::string& plain_string(test_table[i + 1]); + size_t encoded_size = HuffmanSize(plain_string); + EXPECT_EQ(huffman_encoded.size(), encoded_size); + std::string buffer; + buffer.reserve(); + Encode(plain_string, encoded_size, &buffer); + EXPECT_EQ(buffer, huffman_encoded) << "Error encoding " << plain_string; + } +} + +TEST_P(HuffmanEncoderTest, SpecResponseExamples) { + // clang-format off + std::string test_table[] = { + absl::HexStringToBytes("6402"), + "302", + absl::HexStringToBytes("aec3771a4b"), + "private", + absl::HexStringToBytes("d07abe941054d444a8200595040b8166" + "e082a62d1bff"), + "Mon, 21 Oct 2013 20:13:21 GMT", + absl::HexStringToBytes("9d29ad171863c78f0b97c8e9ae82ae43" + "d3"), + "https://www.example.com", + absl::HexStringToBytes("94e7821dd7f2e6c7b335dfdfcd5b3960" + "d5af27087f3672c1ab270fb5291f9587" + "316065c003ed4ee5b1063d5007"), + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", + }; + // clang-format on + for (size_t i = 0; i != ABSL_ARRAYSIZE(test_table); i += 2) { + const std::string& huffman_encoded(test_table[i]); + const std::string& plain_string(test_table[i + 1]); + size_t encoded_size = HuffmanSize(plain_string); + EXPECT_EQ(huffman_encoded.size(), encoded_size); + std::string buffer; + Encode(plain_string, encoded_size, &buffer); + EXPECT_EQ(buffer, huffman_encoded) << "Error encoding " << plain_string; + } +} + +TEST_P(HuffmanEncoderTest, EncodedSizeAgreesWithEncodeString) { + std::string test_table[] = { + "", + "Mon, 21 Oct 2013 20:13:21 GMT", + "https://www.example.com", + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", + std::string(1, '\0'), + std::string("foo\0bar", 7), + std::string(256, '\0'), + }; + // Modify last |test_table| entry to cover all codes. + for (size_t i = 0; i != 256; ++i) { + test_table[ABSL_ARRAYSIZE(test_table) - 1][i] = static_cast<char>(i); + } + + for (size_t i = 0; i != ABSL_ARRAYSIZE(test_table); ++i) { + const std::string& plain_string = test_table[i]; + size_t encoded_size = HuffmanSize(plain_string); + std::string huffman_encoded; + Encode(plain_string, encoded_size, &huffman_encoded); + EXPECT_EQ(encoded_size, huffman_encoded.size()); + } +} + +// Test that encoding appends to output without overwriting it. +TEST_P(HuffmanEncoderTest, AppendToOutput) { + size_t encoded_size = HuffmanSize("foo"); + std::string buffer; + Encode("foo", encoded_size, &buffer); + EXPECT_EQ(absl::HexStringToBytes("94e7"), buffer); + + encoded_size = HuffmanSize("bar"); + Encode("bar", encoded_size, &buffer); + EXPECT_EQ(absl::HexStringToBytes("94e78c767f"), buffer); +} + +} // namespace +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_transcoder_test.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_transcoder_test.cc new file mode 100644 index 00000000000..8d57ec96dcd --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_transcoder_test.cc @@ -0,0 +1,183 @@ +// Copyright (c) 2018 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. + +// A test of roundtrips through the encoder and decoder. + +#include <stddef.h> + +#include "absl/strings/string_view.h" +#include "quiche/http2/decoder/decode_buffer.h" +#include "quiche/http2/decoder/decode_status.h" +#include "quiche/http2/hpack/huffman/hpack_huffman_decoder.h" +#include "quiche/http2/hpack/huffman/hpack_huffman_encoder.h" +#include "quiche/http2/tools/random_decoder_test.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/common/quiche_text_utils.h" + +using ::testing::AssertionSuccess; +using ::testing::Combine; +using ::testing::Range; +using ::testing::Values; + +namespace http2 { +namespace test { +namespace { + +std::string GenAsciiNonControlSet() { + std::string s; + const char space = ' '; // First character after the control characters: 0x20 + const char del = 127; // First character after the non-control characters. + for (char c = space; c < del; ++c) { + s.push_back(c); + } + return s; +} + +class HpackHuffmanTranscoderTest : public RandomDecoderTest { + protected: + HpackHuffmanTranscoderTest() + : ascii_non_control_set_(GenAsciiNonControlSet()) { + // The decoder may return true, and its accumulator may be empty, at + // many boundaries while decoding, and yet the whole string hasn't + // been decoded. + stop_decode_on_done_ = false; + } + + DecodeStatus StartDecoding(DecodeBuffer* b) override { + input_bytes_seen_ = 0; + output_buffer_.clear(); + decoder_.Reset(); + return ResumeDecoding(b); + } + + DecodeStatus ResumeDecoding(DecodeBuffer* b) override { + input_bytes_seen_ += b->Remaining(); + absl::string_view sp(b->cursor(), b->Remaining()); + if (decoder_.Decode(sp, &output_buffer_)) { + b->AdvanceCursor(b->Remaining()); + // Successfully decoded (or buffered) the bytes in absl::string_view. + EXPECT_LE(input_bytes_seen_, input_bytes_expected_); + // Have we reached the end of the encoded string? + if (input_bytes_expected_ == input_bytes_seen_) { + if (decoder_.InputProperlyTerminated()) { + return DecodeStatus::kDecodeDone; + } else { + return DecodeStatus::kDecodeError; + } + } + return DecodeStatus::kDecodeInProgress; + } + return DecodeStatus::kDecodeError; + } + + AssertionResult TranscodeAndValidateSeveralWays( + absl::string_view plain, + absl::string_view expected_huffman) { + size_t encoded_size = HuffmanSize(plain); + std::string encoded; + HuffmanEncode(plain, encoded_size, &encoded); + VERIFY_EQ(encoded_size, encoded.size()); + if (!expected_huffman.empty() || plain.empty()) { + VERIFY_EQ(encoded, expected_huffman); + } + input_bytes_expected_ = encoded.size(); + auto validator = [plain, this]() -> AssertionResult { + VERIFY_EQ(output_buffer_.size(), plain.size()); + VERIFY_EQ(output_buffer_, plain); + return AssertionSuccess(); + }; + DecodeBuffer db(encoded); + bool return_non_zero_on_first = false; + return DecodeAndValidateSeveralWays(&db, return_non_zero_on_first, + ValidateDoneAndEmpty(validator)); + } + + AssertionResult TranscodeAndValidateSeveralWays(absl::string_view plain) { + return TranscodeAndValidateSeveralWays(plain, ""); + } + + std::string RandomAsciiNonControlString(int length) { + return Random().RandStringWithAlphabet(length, ascii_non_control_set_); + } + + std::string RandomBytes(int length) { return Random().RandString(length); } + + const std::string ascii_non_control_set_; + HpackHuffmanDecoder decoder_; + std::string output_buffer_; + size_t input_bytes_seen_; + size_t input_bytes_expected_; +}; + +TEST_F(HpackHuffmanTranscoderTest, RoundTripRandomAsciiNonControlString) { + for (size_t length = 0; length != 20; length++) { + const std::string s = RandomAsciiNonControlString(length); + ASSERT_TRUE(TranscodeAndValidateSeveralWays(s)) + << "Unable to decode:\n\n" + << quiche::QuicheTextUtils::HexDump(s) << "\n\noutput_buffer_:\n" + << quiche::QuicheTextUtils::HexDump(output_buffer_); + } +} + +TEST_F(HpackHuffmanTranscoderTest, RoundTripRandomBytes) { + for (size_t length = 0; length != 20; length++) { + const std::string s = RandomBytes(length); + ASSERT_TRUE(TranscodeAndValidateSeveralWays(s)) + << "Unable to decode:\n\n" + << quiche::QuicheTextUtils::HexDump(s) << "\n\noutput_buffer_:\n" + << quiche::QuicheTextUtils::HexDump(output_buffer_); + } +} + +// Two parameters: decoder choice, and the character to round-trip. +class HpackHuffmanTranscoderAdjacentCharTest + : public HpackHuffmanTranscoderTest, + public testing::WithParamInterface<int> { + protected: + HpackHuffmanTranscoderAdjacentCharTest() + : c_(static_cast<char>(GetParam())) {} + + const char c_; +}; + +INSTANTIATE_TEST_SUITE_P(HpackHuffmanTranscoderAdjacentCharTest, + HpackHuffmanTranscoderAdjacentCharTest, Range(0, 256)); + +// Test c_ adjacent to every other character, both before and after. +TEST_P(HpackHuffmanTranscoderAdjacentCharTest, RoundTripAdjacentChar) { + std::string s; + for (int a = 0; a < 256; ++a) { + s.push_back(static_cast<char>(a)); + s.push_back(c_); + s.push_back(static_cast<char>(a)); + } + ASSERT_TRUE(TranscodeAndValidateSeveralWays(s)); +} + +// Two parameters: character to repeat, number of repeats. +class HpackHuffmanTranscoderRepeatedCharTest + : public HpackHuffmanTranscoderTest, + public testing::WithParamInterface<std::tuple<int, int>> { + protected: + HpackHuffmanTranscoderRepeatedCharTest() + : c_(static_cast<char>(std::get<0>(GetParam()))), + length_(std::get<1>(GetParam())) {} + std::string MakeString() { return std::string(length_, c_); } + + private: + const char c_; + const size_t length_; +}; + +INSTANTIATE_TEST_SUITE_P(HpackHuffmanTranscoderRepeatedCharTest, + HpackHuffmanTranscoderRepeatedCharTest, + Combine(Range(0, 256), Values(1, 2, 3, 4, 8, 16, 32))); + +TEST_P(HpackHuffmanTranscoderRepeatedCharTest, RoundTripRepeatedChar) { + ASSERT_TRUE(TranscodeAndValidateSeveralWays(MakeString())); +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/huffman_spec_tables.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/huffman_spec_tables.cc new file mode 100644 index 00000000000..f4b103b93f6 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/huffman_spec_tables.cc @@ -0,0 +1,572 @@ +// Copyright (c) 2018 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/hpack/huffman/huffman_spec_tables.h" + +namespace http2 { + +// clang-format off +// static +const uint8_t HuffmanSpecTables::kCodeLengths[] = { + 13, 23, 28, 28, 28, 28, 28, 28, // 0 - 7 + 28, 24, 30, 28, 28, 30, 28, 28, // 8 - 15 + 28, 28, 28, 28, 28, 28, 30, 28, // 16 - 23 + 28, 28, 28, 28, 28, 28, 28, 28, // 24 - 31 + 6, 10, 10, 12, 13, 6, 8, 11, // 32 - 39 + 10, 10, 8, 11, 8, 6, 6, 6, // 40 - 47 + 5, 5, 5, 6, 6, 6, 6, 6, // 48 - 55 + 6, 6, 7, 8, 15, 6, 12, 10, // 56 - 63 + 13, 6, 7, 7, 7, 7, 7, 7, // 64 - 71 + 7, 7, 7, 7, 7, 7, 7, 7, // 72 - 79 + 7, 7, 7, 7, 7, 7, 7, 7, // 80 - 87 + 8, 7, 8, 13, 19, 13, 14, 6, // 88 - 95 + 15, 5, 6, 5, 6, 5, 6, 6, // 96 - 103 + 6, 5, 7, 7, 6, 6, 6, 5, // 104 - 111 + 6, 7, 6, 5, 5, 6, 7, 7, // 112 - 119 + 7, 7, 7, 15, 11, 14, 13, 28, // 120 - 127 + 20, 22, 20, 20, 22, 22, 22, 23, // 128 - 135 + 22, 23, 23, 23, 23, 23, 24, 23, // 136 - 143 + 24, 24, 22, 23, 24, 23, 23, 23, // 144 - 151 + 23, 21, 22, 23, 22, 23, 23, 24, // 152 - 159 + 22, 21, 20, 22, 22, 23, 23, 21, // 160 - 167 + 23, 22, 22, 24, 21, 22, 23, 23, // 168 - 175 + 21, 21, 22, 21, 23, 22, 23, 23, // 176 - 183 + 20, 22, 22, 22, 23, 22, 22, 23, // 184 - 191 + 26, 26, 20, 19, 22, 23, 22, 25, // 192 - 199 + 26, 26, 26, 27, 27, 26, 24, 25, // 200 - 207 + 19, 21, 26, 27, 27, 26, 27, 24, // 208 - 215 + 21, 21, 26, 26, 28, 27, 27, 27, // 216 - 223 + 20, 24, 20, 21, 22, 21, 21, 23, // 224 - 231 + 22, 22, 25, 25, 24, 24, 26, 23, // 232 - 239 + 26, 27, 26, 26, 27, 27, 27, 27, // 240 - 247 + 27, 28, 27, 27, 27, 27, 27, 26, // 248 - 255 + 30, // 256 +}; + +// The encoding of each symbol, left justified (as printed), which means that +// the first bit of the encoding is the high-order bit of the uint32. +// static +const uint32_t HuffmanSpecTables::kLeftCodes[] = { + 0b11111111110000000000000000000000, // 0x00 + 0b11111111111111111011000000000000, // 0x01 + 0b11111111111111111111111000100000, // 0x02 + 0b11111111111111111111111000110000, // 0x03 + 0b11111111111111111111111001000000, // 0x04 + 0b11111111111111111111111001010000, // 0x05 + 0b11111111111111111111111001100000, // 0x06 + 0b11111111111111111111111001110000, // 0x07 + 0b11111111111111111111111010000000, // 0x08 + 0b11111111111111111110101000000000, // 0x09 + 0b11111111111111111111111111110000, // 0x0a + 0b11111111111111111111111010010000, // 0x0b + 0b11111111111111111111111010100000, // 0x0c + 0b11111111111111111111111111110100, // 0x0d + 0b11111111111111111111111010110000, // 0x0e + 0b11111111111111111111111011000000, // 0x0f + 0b11111111111111111111111011010000, // 0x10 + 0b11111111111111111111111011100000, // 0x11 + 0b11111111111111111111111011110000, // 0x12 + 0b11111111111111111111111100000000, // 0x13 + 0b11111111111111111111111100010000, // 0x14 + 0b11111111111111111111111100100000, // 0x15 + 0b11111111111111111111111111111000, // 0x16 + 0b11111111111111111111111100110000, // 0x17 + 0b11111111111111111111111101000000, // 0x18 + 0b11111111111111111111111101010000, // 0x19 + 0b11111111111111111111111101100000, // 0x1a + 0b11111111111111111111111101110000, // 0x1b + 0b11111111111111111111111110000000, // 0x1c + 0b11111111111111111111111110010000, // 0x1d + 0b11111111111111111111111110100000, // 0x1e + 0b11111111111111111111111110110000, // 0x1f + 0b01010000000000000000000000000000, // 0x20 + 0b11111110000000000000000000000000, // '!' + 0b11111110010000000000000000000000, // '\"' + 0b11111111101000000000000000000000, // '#' + 0b11111111110010000000000000000000, // '$' + 0b01010100000000000000000000000000, // '%' + 0b11111000000000000000000000000000, // '&' + 0b11111111010000000000000000000000, // '\'' + 0b11111110100000000000000000000000, // '(' + 0b11111110110000000000000000000000, // ')' + 0b11111001000000000000000000000000, // '*' + 0b11111111011000000000000000000000, // '+' + 0b11111010000000000000000000000000, // ',' + 0b01011000000000000000000000000000, // '-' + 0b01011100000000000000000000000000, // '.' + 0b01100000000000000000000000000000, // '/' + 0b00000000000000000000000000000000, // '0' + 0b00001000000000000000000000000000, // '1' + 0b00010000000000000000000000000000, // '2' + 0b01100100000000000000000000000000, // '3' + 0b01101000000000000000000000000000, // '4' + 0b01101100000000000000000000000000, // '5' + 0b01110000000000000000000000000000, // '6' + 0b01110100000000000000000000000000, // '7' + 0b01111000000000000000000000000000, // '8' + 0b01111100000000000000000000000000, // '9' + 0b10111000000000000000000000000000, // ':' + 0b11111011000000000000000000000000, // ';' + 0b11111111111110000000000000000000, // '<' + 0b10000000000000000000000000000000, // '=' + 0b11111111101100000000000000000000, // '>' + 0b11111111000000000000000000000000, // '?' + 0b11111111110100000000000000000000, // '@' + 0b10000100000000000000000000000000, // 'A' + 0b10111010000000000000000000000000, // 'B' + 0b10111100000000000000000000000000, // 'C' + 0b10111110000000000000000000000000, // 'D' + 0b11000000000000000000000000000000, // 'E' + 0b11000010000000000000000000000000, // 'F' + 0b11000100000000000000000000000000, // 'G' + 0b11000110000000000000000000000000, // 'H' + 0b11001000000000000000000000000000, // 'I' + 0b11001010000000000000000000000000, // 'J' + 0b11001100000000000000000000000000, // 'K' + 0b11001110000000000000000000000000, // 'L' + 0b11010000000000000000000000000000, // 'M' + 0b11010010000000000000000000000000, // 'N' + 0b11010100000000000000000000000000, // 'O' + 0b11010110000000000000000000000000, // 'P' + 0b11011000000000000000000000000000, // 'Q' + 0b11011010000000000000000000000000, // 'R' + 0b11011100000000000000000000000000, // 'S' + 0b11011110000000000000000000000000, // 'T' + 0b11100000000000000000000000000000, // 'U' + 0b11100010000000000000000000000000, // 'V' + 0b11100100000000000000000000000000, // 'W' + 0b11111100000000000000000000000000, // 'X' + 0b11100110000000000000000000000000, // 'Y' + 0b11111101000000000000000000000000, // 'Z' + 0b11111111110110000000000000000000, // '[' + 0b11111111111111100000000000000000, // '\\' + 0b11111111111000000000000000000000, // ']' + 0b11111111111100000000000000000000, // '^' + 0b10001000000000000000000000000000, // '_' + 0b11111111111110100000000000000000, // '`' + 0b00011000000000000000000000000000, // 'a' + 0b10001100000000000000000000000000, // 'b' + 0b00100000000000000000000000000000, // 'c' + 0b10010000000000000000000000000000, // 'd' + 0b00101000000000000000000000000000, // 'e' + 0b10010100000000000000000000000000, // 'f' + 0b10011000000000000000000000000000, // 'g' + 0b10011100000000000000000000000000, // 'h' + 0b00110000000000000000000000000000, // 'i' + 0b11101000000000000000000000000000, // 'j' + 0b11101010000000000000000000000000, // 'k' + 0b10100000000000000000000000000000, // 'l' + 0b10100100000000000000000000000000, // 'm' + 0b10101000000000000000000000000000, // 'n' + 0b00111000000000000000000000000000, // 'o' + 0b10101100000000000000000000000000, // 'p' + 0b11101100000000000000000000000000, // 'q' + 0b10110000000000000000000000000000, // 'r' + 0b01000000000000000000000000000000, // 's' + 0b01001000000000000000000000000000, // 't' + 0b10110100000000000000000000000000, // 'u' + 0b11101110000000000000000000000000, // 'v' + 0b11110000000000000000000000000000, // 'w' + 0b11110010000000000000000000000000, // 'x' + 0b11110100000000000000000000000000, // 'y' + 0b11110110000000000000000000000000, // 'z' + 0b11111111111111000000000000000000, // '{' + 0b11111111100000000000000000000000, // '|' + 0b11111111111101000000000000000000, // '}' + 0b11111111111010000000000000000000, // '~' + 0b11111111111111111111111111000000, // 0x7f + 0b11111111111111100110000000000000, // 0x80 + 0b11111111111111110100100000000000, // 0x81 + 0b11111111111111100111000000000000, // 0x82 + 0b11111111111111101000000000000000, // 0x83 + 0b11111111111111110100110000000000, // 0x84 + 0b11111111111111110101000000000000, // 0x85 + 0b11111111111111110101010000000000, // 0x86 + 0b11111111111111111011001000000000, // 0x87 + 0b11111111111111110101100000000000, // 0x88 + 0b11111111111111111011010000000000, // 0x89 + 0b11111111111111111011011000000000, // 0x8a + 0b11111111111111111011100000000000, // 0x8b + 0b11111111111111111011101000000000, // 0x8c + 0b11111111111111111011110000000000, // 0x8d + 0b11111111111111111110101100000000, // 0x8e + 0b11111111111111111011111000000000, // 0x8f + 0b11111111111111111110110000000000, // 0x90 + 0b11111111111111111110110100000000, // 0x91 + 0b11111111111111110101110000000000, // 0x92 + 0b11111111111111111100000000000000, // 0x93 + 0b11111111111111111110111000000000, // 0x94 + 0b11111111111111111100001000000000, // 0x95 + 0b11111111111111111100010000000000, // 0x96 + 0b11111111111111111100011000000000, // 0x97 + 0b11111111111111111100100000000000, // 0x98 + 0b11111111111111101110000000000000, // 0x99 + 0b11111111111111110110000000000000, // 0x9a + 0b11111111111111111100101000000000, // 0x9b + 0b11111111111111110110010000000000, // 0x9c + 0b11111111111111111100110000000000, // 0x9d + 0b11111111111111111100111000000000, // 0x9e + 0b11111111111111111110111100000000, // 0x9f + 0b11111111111111110110100000000000, // 0xa0 + 0b11111111111111101110100000000000, // 0xa1 + 0b11111111111111101001000000000000, // 0xa2 + 0b11111111111111110110110000000000, // 0xa3 + 0b11111111111111110111000000000000, // 0xa4 + 0b11111111111111111101000000000000, // 0xa5 + 0b11111111111111111101001000000000, // 0xa6 + 0b11111111111111101111000000000000, // 0xa7 + 0b11111111111111111101010000000000, // 0xa8 + 0b11111111111111110111010000000000, // 0xa9 + 0b11111111111111110111100000000000, // 0xaa + 0b11111111111111111111000000000000, // 0xab + 0b11111111111111101111100000000000, // 0xac + 0b11111111111111110111110000000000, // 0xad + 0b11111111111111111101011000000000, // 0xae + 0b11111111111111111101100000000000, // 0xaf + 0b11111111111111110000000000000000, // 0xb0 + 0b11111111111111110000100000000000, // 0xb1 + 0b11111111111111111000000000000000, // 0xb2 + 0b11111111111111110001000000000000, // 0xb3 + 0b11111111111111111101101000000000, // 0xb4 + 0b11111111111111111000010000000000, // 0xb5 + 0b11111111111111111101110000000000, // 0xb6 + 0b11111111111111111101111000000000, // 0xb7 + 0b11111111111111101010000000000000, // 0xb8 + 0b11111111111111111000100000000000, // 0xb9 + 0b11111111111111111000110000000000, // 0xba + 0b11111111111111111001000000000000, // 0xbb + 0b11111111111111111110000000000000, // 0xbc + 0b11111111111111111001010000000000, // 0xbd + 0b11111111111111111001100000000000, // 0xbe + 0b11111111111111111110001000000000, // 0xbf + 0b11111111111111111111100000000000, // 0xc0 + 0b11111111111111111111100001000000, // 0xc1 + 0b11111111111111101011000000000000, // 0xc2 + 0b11111111111111100010000000000000, // 0xc3 + 0b11111111111111111001110000000000, // 0xc4 + 0b11111111111111111110010000000000, // 0xc5 + 0b11111111111111111010000000000000, // 0xc6 + 0b11111111111111111111011000000000, // 0xc7 + 0b11111111111111111111100010000000, // 0xc8 + 0b11111111111111111111100011000000, // 0xc9 + 0b11111111111111111111100100000000, // 0xca + 0b11111111111111111111101111000000, // 0xcb + 0b11111111111111111111101111100000, // 0xcc + 0b11111111111111111111100101000000, // 0xcd + 0b11111111111111111111000100000000, // 0xce + 0b11111111111111111111011010000000, // 0xcf + 0b11111111111111100100000000000000, // 0xd0 + 0b11111111111111110001100000000000, // 0xd1 + 0b11111111111111111111100110000000, // 0xd2 + 0b11111111111111111111110000000000, // 0xd3 + 0b11111111111111111111110000100000, // 0xd4 + 0b11111111111111111111100111000000, // 0xd5 + 0b11111111111111111111110001000000, // 0xd6 + 0b11111111111111111111001000000000, // 0xd7 + 0b11111111111111110010000000000000, // 0xd8 + 0b11111111111111110010100000000000, // 0xd9 + 0b11111111111111111111101000000000, // 0xda + 0b11111111111111111111101001000000, // 0xdb + 0b11111111111111111111111111010000, // 0xdc + 0b11111111111111111111110001100000, // 0xdd + 0b11111111111111111111110010000000, // 0xde + 0b11111111111111111111110010100000, // 0xdf + 0b11111111111111101100000000000000, // 0xe0 + 0b11111111111111111111001100000000, // 0xe1 + 0b11111111111111101101000000000000, // 0xe2 + 0b11111111111111110011000000000000, // 0xe3 + 0b11111111111111111010010000000000, // 0xe4 + 0b11111111111111110011100000000000, // 0xe5 + 0b11111111111111110100000000000000, // 0xe6 + 0b11111111111111111110011000000000, // 0xe7 + 0b11111111111111111010100000000000, // 0xe8 + 0b11111111111111111010110000000000, // 0xe9 + 0b11111111111111111111011100000000, // 0xea + 0b11111111111111111111011110000000, // 0xeb + 0b11111111111111111111010000000000, // 0xec + 0b11111111111111111111010100000000, // 0xed + 0b11111111111111111111101010000000, // 0xee + 0b11111111111111111110100000000000, // 0xef + 0b11111111111111111111101011000000, // 0xf0 + 0b11111111111111111111110011000000, // 0xf1 + 0b11111111111111111111101100000000, // 0xf2 + 0b11111111111111111111101101000000, // 0xf3 + 0b11111111111111111111110011100000, // 0xf4 + 0b11111111111111111111110100000000, // 0xf5 + 0b11111111111111111111110100100000, // 0xf6 + 0b11111111111111111111110101000000, // 0xf7 + 0b11111111111111111111110101100000, // 0xf8 + 0b11111111111111111111111111100000, // 0xf9 + 0b11111111111111111111110110000000, // 0xfa + 0b11111111111111111111110110100000, // 0xfb + 0b11111111111111111111110111000000, // 0xfc + 0b11111111111111111111110111100000, // 0xfd + 0b11111111111111111111111000000000, // 0xfe + 0b11111111111111111111101110000000, // 0xff + 0b11111111111111111111111111111100, // 0x100 +}; + +// static +const uint32_t HuffmanSpecTables::kRightCodes[] = { + 0b00000000000000000001111111111000, // 0x00 + 0b00000000011111111111111111011000, // 0x01 + 0b00001111111111111111111111100010, // 0x02 + 0b00001111111111111111111111100011, // 0x03 + 0b00001111111111111111111111100100, // 0x04 + 0b00001111111111111111111111100101, // 0x05 + 0b00001111111111111111111111100110, // 0x06 + 0b00001111111111111111111111100111, // 0x07 + 0b00001111111111111111111111101000, // 0x08 + 0b00000000111111111111111111101010, // 0x09 + 0b00111111111111111111111111111100, // 0x0a + 0b00001111111111111111111111101001, // 0x0b + 0b00001111111111111111111111101010, // 0x0c + 0b00111111111111111111111111111101, // 0x0d + 0b00001111111111111111111111101011, // 0x0e + 0b00001111111111111111111111101100, // 0x0f + 0b00001111111111111111111111101101, // 0x10 + 0b00001111111111111111111111101110, // 0x11 + 0b00001111111111111111111111101111, // 0x12 + 0b00001111111111111111111111110000, // 0x13 + 0b00001111111111111111111111110001, // 0x14 + 0b00001111111111111111111111110010, // 0x15 + 0b00111111111111111111111111111110, // 0x16 + 0b00001111111111111111111111110011, // 0x17 + 0b00001111111111111111111111110100, // 0x18 + 0b00001111111111111111111111110101, // 0x19 + 0b00001111111111111111111111110110, // 0x1a + 0b00001111111111111111111111110111, // 0x1b + 0b00001111111111111111111111111000, // 0x1c + 0b00001111111111111111111111111001, // 0x1d + 0b00001111111111111111111111111010, // 0x1e + 0b00001111111111111111111111111011, // 0x1f + 0b00000000000000000000000000010100, // 0x20 + 0b00000000000000000000001111111000, // '!' + 0b00000000000000000000001111111001, // '\"' + 0b00000000000000000000111111111010, // '#' + 0b00000000000000000001111111111001, // '$' + 0b00000000000000000000000000010101, // '%' + 0b00000000000000000000000011111000, // '&' + 0b00000000000000000000011111111010, // '\'' + 0b00000000000000000000001111111010, // '(' + 0b00000000000000000000001111111011, // ')' + 0b00000000000000000000000011111001, // '*' + 0b00000000000000000000011111111011, // '+' + 0b00000000000000000000000011111010, // ',' + 0b00000000000000000000000000010110, // '-' + 0b00000000000000000000000000010111, // '.' + 0b00000000000000000000000000011000, // '/' + 0b00000000000000000000000000000000, // '0' + 0b00000000000000000000000000000001, // '1' + 0b00000000000000000000000000000010, // '2' + 0b00000000000000000000000000011001, // '3' + 0b00000000000000000000000000011010, // '4' + 0b00000000000000000000000000011011, // '5' + 0b00000000000000000000000000011100, // '6' + 0b00000000000000000000000000011101, // '7' + 0b00000000000000000000000000011110, // '8' + 0b00000000000000000000000000011111, // '9' + 0b00000000000000000000000001011100, // ':' + 0b00000000000000000000000011111011, // ';' + 0b00000000000000000111111111111100, // '<' + 0b00000000000000000000000000100000, // '=' + 0b00000000000000000000111111111011, // '>' + 0b00000000000000000000001111111100, // '?' + 0b00000000000000000001111111111010, // '@' + 0b00000000000000000000000000100001, // 'A' + 0b00000000000000000000000001011101, // 'B' + 0b00000000000000000000000001011110, // 'C' + 0b00000000000000000000000001011111, // 'D' + 0b00000000000000000000000001100000, // 'E' + 0b00000000000000000000000001100001, // 'F' + 0b00000000000000000000000001100010, // 'G' + 0b00000000000000000000000001100011, // 'H' + 0b00000000000000000000000001100100, // 'I' + 0b00000000000000000000000001100101, // 'J' + 0b00000000000000000000000001100110, // 'K' + 0b00000000000000000000000001100111, // 'L' + 0b00000000000000000000000001101000, // 'M' + 0b00000000000000000000000001101001, // 'N' + 0b00000000000000000000000001101010, // 'O' + 0b00000000000000000000000001101011, // 'P' + 0b00000000000000000000000001101100, // 'Q' + 0b00000000000000000000000001101101, // 'R' + 0b00000000000000000000000001101110, // 'S' + 0b00000000000000000000000001101111, // 'T' + 0b00000000000000000000000001110000, // 'U' + 0b00000000000000000000000001110001, // 'V' + 0b00000000000000000000000001110010, // 'W' + 0b00000000000000000000000011111100, // 'X' + 0b00000000000000000000000001110011, // 'Y' + 0b00000000000000000000000011111101, // 'Z' + 0b00000000000000000001111111111011, // '[' + 0b00000000000001111111111111110000, // '\\' + 0b00000000000000000001111111111100, // ']' + 0b00000000000000000011111111111100, // '^' + 0b00000000000000000000000000100010, // '_' + 0b00000000000000000111111111111101, // '`' + 0b00000000000000000000000000000011, // 'a' + 0b00000000000000000000000000100011, // 'b' + 0b00000000000000000000000000000100, // 'c' + 0b00000000000000000000000000100100, // 'd' + 0b00000000000000000000000000000101, // 'e' + 0b00000000000000000000000000100101, // 'f' + 0b00000000000000000000000000100110, // 'g' + 0b00000000000000000000000000100111, // 'h' + 0b00000000000000000000000000000110, // 'i' + 0b00000000000000000000000001110100, // 'j' + 0b00000000000000000000000001110101, // 'k' + 0b00000000000000000000000000101000, // 'l' + 0b00000000000000000000000000101001, // 'm' + 0b00000000000000000000000000101010, // 'n' + 0b00000000000000000000000000000111, // 'o' + 0b00000000000000000000000000101011, // 'p' + 0b00000000000000000000000001110110, // 'q' + 0b00000000000000000000000000101100, // 'r' + 0b00000000000000000000000000001000, // 's' + 0b00000000000000000000000000001001, // 't' + 0b00000000000000000000000000101101, // 'u' + 0b00000000000000000000000001110111, // 'v' + 0b00000000000000000000000001111000, // 'w' + 0b00000000000000000000000001111001, // 'x' + 0b00000000000000000000000001111010, // 'y' + 0b00000000000000000000000001111011, // 'z' + 0b00000000000000000111111111111110, // '{' + 0b00000000000000000000011111111100, // '|' + 0b00000000000000000011111111111101, // '}' + 0b00000000000000000001111111111101, // '~' + 0b00001111111111111111111111111100, // 0x7f + 0b00000000000011111111111111100110, // 0x80 + 0b00000000001111111111111111010010, // 0x81 + 0b00000000000011111111111111100111, // 0x82 + 0b00000000000011111111111111101000, // 0x83 + 0b00000000001111111111111111010011, // 0x84 + 0b00000000001111111111111111010100, // 0x85 + 0b00000000001111111111111111010101, // 0x86 + 0b00000000011111111111111111011001, // 0x87 + 0b00000000001111111111111111010110, // 0x88 + 0b00000000011111111111111111011010, // 0x89 + 0b00000000011111111111111111011011, // 0x8a + 0b00000000011111111111111111011100, // 0x8b + 0b00000000011111111111111111011101, // 0x8c + 0b00000000011111111111111111011110, // 0x8d + 0b00000000111111111111111111101011, // 0x8e + 0b00000000011111111111111111011111, // 0x8f + 0b00000000111111111111111111101100, // 0x90 + 0b00000000111111111111111111101101, // 0x91 + 0b00000000001111111111111111010111, // 0x92 + 0b00000000011111111111111111100000, // 0x93 + 0b00000000111111111111111111101110, // 0x94 + 0b00000000011111111111111111100001, // 0x95 + 0b00000000011111111111111111100010, // 0x96 + 0b00000000011111111111111111100011, // 0x97 + 0b00000000011111111111111111100100, // 0x98 + 0b00000000000111111111111111011100, // 0x99 + 0b00000000001111111111111111011000, // 0x9a + 0b00000000011111111111111111100101, // 0x9b + 0b00000000001111111111111111011001, // 0x9c + 0b00000000011111111111111111100110, // 0x9d + 0b00000000011111111111111111100111, // 0x9e + 0b00000000111111111111111111101111, // 0x9f + 0b00000000001111111111111111011010, // 0xa0 + 0b00000000000111111111111111011101, // 0xa1 + 0b00000000000011111111111111101001, // 0xa2 + 0b00000000001111111111111111011011, // 0xa3 + 0b00000000001111111111111111011100, // 0xa4 + 0b00000000011111111111111111101000, // 0xa5 + 0b00000000011111111111111111101001, // 0xa6 + 0b00000000000111111111111111011110, // 0xa7 + 0b00000000011111111111111111101010, // 0xa8 + 0b00000000001111111111111111011101, // 0xa9 + 0b00000000001111111111111111011110, // 0xaa + 0b00000000111111111111111111110000, // 0xab + 0b00000000000111111111111111011111, // 0xac + 0b00000000001111111111111111011111, // 0xad + 0b00000000011111111111111111101011, // 0xae + 0b00000000011111111111111111101100, // 0xaf + 0b00000000000111111111111111100000, // 0xb0 + 0b00000000000111111111111111100001, // 0xb1 + 0b00000000001111111111111111100000, // 0xb2 + 0b00000000000111111111111111100010, // 0xb3 + 0b00000000011111111111111111101101, // 0xb4 + 0b00000000001111111111111111100001, // 0xb5 + 0b00000000011111111111111111101110, // 0xb6 + 0b00000000011111111111111111101111, // 0xb7 + 0b00000000000011111111111111101010, // 0xb8 + 0b00000000001111111111111111100010, // 0xb9 + 0b00000000001111111111111111100011, // 0xba + 0b00000000001111111111111111100100, // 0xbb + 0b00000000011111111111111111110000, // 0xbc + 0b00000000001111111111111111100101, // 0xbd + 0b00000000001111111111111111100110, // 0xbe + 0b00000000011111111111111111110001, // 0xbf + 0b00000011111111111111111111100000, // 0xc0 + 0b00000011111111111111111111100001, // 0xc1 + 0b00000000000011111111111111101011, // 0xc2 + 0b00000000000001111111111111110001, // 0xc3 + 0b00000000001111111111111111100111, // 0xc4 + 0b00000000011111111111111111110010, // 0xc5 + 0b00000000001111111111111111101000, // 0xc6 + 0b00000001111111111111111111101100, // 0xc7 + 0b00000011111111111111111111100010, // 0xc8 + 0b00000011111111111111111111100011, // 0xc9 + 0b00000011111111111111111111100100, // 0xca + 0b00000111111111111111111111011110, // 0xcb + 0b00000111111111111111111111011111, // 0xcc + 0b00000011111111111111111111100101, // 0xcd + 0b00000000111111111111111111110001, // 0xce + 0b00000001111111111111111111101101, // 0xcf + 0b00000000000001111111111111110010, // 0xd0 + 0b00000000000111111111111111100011, // 0xd1 + 0b00000011111111111111111111100110, // 0xd2 + 0b00000111111111111111111111100000, // 0xd3 + 0b00000111111111111111111111100001, // 0xd4 + 0b00000011111111111111111111100111, // 0xd5 + 0b00000111111111111111111111100010, // 0xd6 + 0b00000000111111111111111111110010, // 0xd7 + 0b00000000000111111111111111100100, // 0xd8 + 0b00000000000111111111111111100101, // 0xd9 + 0b00000011111111111111111111101000, // 0xda + 0b00000011111111111111111111101001, // 0xdb + 0b00001111111111111111111111111101, // 0xdc + 0b00000111111111111111111111100011, // 0xdd + 0b00000111111111111111111111100100, // 0xde + 0b00000111111111111111111111100101, // 0xdf + 0b00000000000011111111111111101100, // 0xe0 + 0b00000000111111111111111111110011, // 0xe1 + 0b00000000000011111111111111101101, // 0xe2 + 0b00000000000111111111111111100110, // 0xe3 + 0b00000000001111111111111111101001, // 0xe4 + 0b00000000000111111111111111100111, // 0xe5 + 0b00000000000111111111111111101000, // 0xe6 + 0b00000000011111111111111111110011, // 0xe7 + 0b00000000001111111111111111101010, // 0xe8 + 0b00000000001111111111111111101011, // 0xe9 + 0b00000001111111111111111111101110, // 0xea + 0b00000001111111111111111111101111, // 0xeb + 0b00000000111111111111111111110100, // 0xec + 0b00000000111111111111111111110101, // 0xed + 0b00000011111111111111111111101010, // 0xee + 0b00000000011111111111111111110100, // 0xef + 0b00000011111111111111111111101011, // 0xf0 + 0b00000111111111111111111111100110, // 0xf1 + 0b00000011111111111111111111101100, // 0xf2 + 0b00000011111111111111111111101101, // 0xf3 + 0b00000111111111111111111111100111, // 0xf4 + 0b00000111111111111111111111101000, // 0xf5 + 0b00000111111111111111111111101001, // 0xf6 + 0b00000111111111111111111111101010, // 0xf7 + 0b00000111111111111111111111101011, // 0xf8 + 0b00001111111111111111111111111110, // 0xf9 + 0b00000111111111111111111111101100, // 0xfa + 0b00000111111111111111111111101101, // 0xfb + 0b00000111111111111111111111101110, // 0xfc + 0b00000111111111111111111111101111, // 0xfd + 0b00000111111111111111111111110000, // 0xfe + 0b00000011111111111111111111101110, // 0xff + 0b00111111111111111111111111111111, // 0x100 +}; +// clang-format off + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/huffman_spec_tables.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/huffman_spec_tables.h new file mode 100644 index 00000000000..d1b144b1358 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/huffman/huffman_spec_tables.h @@ -0,0 +1,29 @@ +// Copyright (c) 2018 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_HPACK_HUFFMAN_HUFFMAN_SPEC_TABLES_H_ +#define QUICHE_HTTP2_HPACK_HUFFMAN_HUFFMAN_SPEC_TABLES_H_ + +// Tables describing the Huffman encoding of bytes as specified by RFC7541. + +#include <cstdint> + +namespace http2 { + +struct HuffmanSpecTables { + // Number of bits in the encoding of each symbol (byte). + static const uint8_t kCodeLengths[257]; + + // The encoding of each symbol, right justified (as printed), which means that + // the last bit of the encoding is the low-order bit of the uint32. + static const uint32_t kRightCodes[257]; + + // The encoding of each symbol, left justified (as printed), which means that + // the first bit of the encoding is the high-order bit of the uint32. + static const uint32_t kLeftCodes[257]; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_HUFFMAN_HUFFMAN_SPEC_TABLES_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/tools/hpack_block_builder.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/tools/hpack_block_builder.cc new file mode 100644 index 00000000000..d958b7c7832 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/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/hpack/tools/hpack_block_builder.h" + +#include "quiche/http2/hpack/varint/hpack_varint_encoder.h" +#include "quiche/http2/platform/api/http2_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: + HTTP2_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/chromium/net/third_party/quiche/src/quiche/http2/hpack/tools/hpack_block_builder.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/tools/hpack_block_builder.h new file mode 100644 index 00000000000..c385a7d9a13 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/tools/hpack_block_builder.h @@ -0,0 +1,98 @@ +// 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_HPACK_TOOLS_HPACK_BLOCK_BUILDER_H_ +#define QUICHE_HTTP2_HPACK_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_HPACK_TOOLS_HPACK_BLOCK_BUILDER_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/tools/hpack_block_builder_test.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/tools/hpack_block_builder_test.cc new file mode 100644 index 00000000000..26e42ff844e --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/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/hpack/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/chromium/net/third_party/quiche/src/quiche/http2/hpack/tools/hpack_example.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/tools/hpack_example.cc new file mode 100644 index 00000000000..c6e6d2d66c1 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/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/hpack/tools/hpack_example.h" + +#include <ctype.h> + +#include "absl/strings/escaping.h" +#include "absl/strings/str_cat.h" +#include "quiche/http2/platform/api/http2_bug_tracker.h" +#include "quiche/http2/platform/api/http2_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; + } + HTTP2_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/chromium/net/third_party/quiche/src/quiche/http2/hpack/tools/hpack_example.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/tools/hpack_example.h new file mode 100644 index 00000000000..de203cccbde --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/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_HPACK_TOOLS_HPACK_EXAMPLE_H_ +#define QUICHE_HTTP2_HPACK_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_HPACK_TOOLS_HPACK_EXAMPLE_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_decoder.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_decoder.cc new file mode 100644 index 00000000000..33348073f44 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_decoder.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/hpack/varint/hpack_varint_decoder.h" + +#include "absl/strings/str_cat.h" + +namespace http2 { + +DecodeStatus HpackVarintDecoder::Start(uint8_t prefix_value, + uint8_t prefix_length, + DecodeBuffer* db) { + QUICHE_DCHECK_LE(3u, prefix_length); + QUICHE_DCHECK_LE(prefix_length, 8u); + + // |prefix_mask| defines the sequence of low-order bits of the first byte + // that encode the prefix of the value. It is also the marker in those bits + // of the first byte indicating that at least one extension byte is needed. + const uint8_t prefix_mask = (1 << prefix_length) - 1; + + // Ignore the bits that aren't a part of the prefix of the varint. + value_ = prefix_value & prefix_mask; + + if (value_ < prefix_mask) { + MarkDone(); + return DecodeStatus::kDecodeDone; + } + + offset_ = 0; + return Resume(db); +} + +DecodeStatus HpackVarintDecoder::StartExtended(uint8_t prefix_length, + DecodeBuffer* db) { + QUICHE_DCHECK_LE(3u, prefix_length); + QUICHE_DCHECK_LE(prefix_length, 8u); + + value_ = (1 << prefix_length) - 1; + offset_ = 0; + return Resume(db); +} + +DecodeStatus HpackVarintDecoder::Resume(DecodeBuffer* db) { + // There can be at most 10 continuation bytes. Offset is zero for the + // first one and increases by 7 for each subsequent one. + const uint8_t kMaxOffset = 63; + CheckNotDone(); + + // Process most extension bytes without the need for overflow checking. + while (offset_ < kMaxOffset) { + if (db->Empty()) { + return DecodeStatus::kDecodeInProgress; + } + + uint8_t byte = db->DecodeUInt8(); + uint64_t summand = byte & 0x7f; + + // Shifting a 7 bit value to the left by at most 56 places can never + // overflow on uint64_t. + QUICHE_DCHECK_LE(offset_, 56); + QUICHE_DCHECK_LE(summand, std::numeric_limits<uint64_t>::max() >> offset_); + + summand <<= offset_; + + // At this point, + // |value_| is at most (2^prefix_length - 1) + (2^49 - 1), and + // |summand| is at most 255 << 56 (which is smaller than 2^63), + // so adding them can never overflow on uint64_t. + QUICHE_DCHECK_LE(value_, std::numeric_limits<uint64_t>::max() - summand); + + value_ += summand; + + // Decoding ends if continuation flag is not set. + if ((byte & 0x80) == 0) { + MarkDone(); + return DecodeStatus::kDecodeDone; + } + + offset_ += 7; + } + + if (db->Empty()) { + return DecodeStatus::kDecodeInProgress; + } + + QUICHE_DCHECK_EQ(kMaxOffset, offset_); + + uint8_t byte = db->DecodeUInt8(); + // No more extension bytes are allowed after this. + if ((byte & 0x80) == 0) { + uint64_t summand = byte & 0x7f; + // Check for overflow in left shift. + if (summand <= std::numeric_limits<uint64_t>::max() >> offset_) { + summand <<= offset_; + // Check for overflow in addition. + if (value_ <= std::numeric_limits<uint64_t>::max() - summand) { + value_ += summand; + MarkDone(); + return DecodeStatus::kDecodeDone; + } + } + } + + // Signal error if value is too large or there are too many extension bytes. + HTTP2_DLOG(WARNING) + << "Variable length int encoding is too large or too long. " + << DebugString(); + MarkDone(); + return DecodeStatus::kDecodeError; +} + +uint64_t HpackVarintDecoder::value() const { + CheckDone(); + return value_; +} + +void HpackVarintDecoder::set_value(uint64_t v) { + MarkDone(); + value_ = v; +} + +std::string HpackVarintDecoder::DebugString() const { + return absl::StrCat("HpackVarintDecoder(value=", value_, ", offset=", offset_, + ")"); +} + +DecodeStatus HpackVarintDecoder::StartForTest(uint8_t prefix_value, + uint8_t prefix_length, + DecodeBuffer* db) { + return Start(prefix_value, prefix_length, db); +} + +DecodeStatus HpackVarintDecoder::StartExtendedForTest(uint8_t prefix_length, + DecodeBuffer* db) { + return StartExtended(prefix_length, db); +} + +DecodeStatus HpackVarintDecoder::ResumeForTest(DecodeBuffer* db) { + return Resume(db); +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_decoder.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_decoder.h new file mode 100644 index 00000000000..d2c8feb3abb --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_decoder.h @@ -0,0 +1,128 @@ +// 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. + +// HpackVarintDecoder decodes HPACK variable length unsigned integers. In HPACK, +// these integers are used to identify static or dynamic table index entries, to +// specify string lengths, and to update the size limit of the dynamic table. +// In QPACK, in addition to these uses, these integers also identify streams. +// +// The caller will need to validate that the decoded value is in an acceptable +// range. +// +// For details of the encoding, see: +// http://httpwg.org/specs/rfc7541.html#integer.representation +// +// HpackVarintDecoder supports decoding any integer that can be represented on +// uint64_t, thereby exceeding the requirements for QPACK: "QPACK +// implementations MUST be able to decode integers up to 62 bits long." See +// https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#rfc.section.5.1.1 +// +// This decoder supports at most 10 extension bytes (bytes following the prefix, +// also called continuation bytes). An encoder is allowed to zero pad the +// encoded integer on the left, thereby increasing the number of extension +// bytes. If an encoder uses so much padding that the number of extension bytes +// exceeds the limit, then this decoder signals an error. + +#ifndef QUICHE_HTTP2_HPACK_VARINT_HPACK_VARINT_DECODER_H_ +#define QUICHE_HTTP2_HPACK_VARINT_HPACK_VARINT_DECODER_H_ + +#include <cstdint> +#include <limits> +#include <string> + +#include "quiche/http2/decoder/decode_buffer.h" +#include "quiche/http2/decoder/decode_status.h" +#include "quiche/http2/platform/api/http2_logging.h" +#include "quiche/common/platform/api/quiche_export.h" + +namespace http2 { + +// Sentinel value for |HpackVarintDecoder::offset_| to signify that decoding is +// completed. Only used in debug builds. +#ifndef NDEBUG +const uint8_t kHpackVarintDecoderOffsetDone = + std::numeric_limits<uint8_t>::max(); +#endif + +// Decodes an HPACK variable length unsigned integer, in a resumable fashion +// so it can handle running out of input in the DecodeBuffer. Call Start or +// StartExtended the first time (when decoding the byte that contains the +// prefix), then call Resume later if it is necessary to resume. When done, +// call value() to retrieve the decoded value. +// +// No constructor or destructor. Holds no resources, so destruction isn't +// needed. Start and StartExtended handles the initialization of member +// variables. This is necessary in order for HpackVarintDecoder to be part +// of a union. +class QUICHE_EXPORT_PRIVATE HpackVarintDecoder { + public: + // |prefix_value| is the first byte of the encoded varint. + // |prefix_length| is number of bits in the first byte that are used for + // encoding the integer. |db| is the rest of the buffer, that is, not + // including the first byte. + DecodeStatus Start(uint8_t prefix_value, uint8_t prefix_length, + DecodeBuffer* db); + + // The caller has already determined that the encoding requires multiple + // bytes, i.e. that the 3 to 8 low-order bits (the number determined by + // |prefix_length|) of the first byte are are all 1. |db| is the rest of the + // buffer, that is, not including the first byte. + DecodeStatus StartExtended(uint8_t prefix_length, DecodeBuffer* db); + + // Resume decoding a variable length integer after an earlier + // call to Start or StartExtended returned kDecodeInProgress. + DecodeStatus Resume(DecodeBuffer* db); + + uint64_t value() const; + + // This supports optimizations for the case of a varint with zero extension + // bytes, where the handling of the prefix is done by the caller. + void set_value(uint64_t v); + + // All the public methods below are for supporting assertions and tests. + + std::string DebugString() const; + + // For benchmarking, these methods ensure the decoder + // is NOT inlined into the caller. + DecodeStatus StartForTest(uint8_t prefix_value, uint8_t prefix_length, + DecodeBuffer* db); + DecodeStatus StartExtendedForTest(uint8_t prefix_length, DecodeBuffer* db); + DecodeStatus ResumeForTest(DecodeBuffer* db); + + private: + // Protection in case Resume is called when it shouldn't be. + void MarkDone() { +#ifndef NDEBUG + offset_ = kHpackVarintDecoderOffsetDone; +#endif + } + void CheckNotDone() const { +#ifndef NDEBUG + QUICHE_DCHECK_NE(kHpackVarintDecoderOffsetDone, offset_); +#endif + } + void CheckDone() const { +#ifndef NDEBUG + QUICHE_DCHECK_EQ(kHpackVarintDecoderOffsetDone, offset_); +#endif + } + + // These fields are initialized just to keep ASAN happy about reading + // them from DebugString(). + + // The encoded integer is being accumulated in |value_|. When decoding is + // complete, |value_| holds the result. + uint64_t value_ = 0; + + // Each extension byte encodes in its lowest 7 bits a segment of the integer. + // |offset_| is the number of places this segment has to be shifted to the + // left for decoding. It is zero for the first extension byte, and increases + // by 7 for each subsequent extension byte. + uint8_t offset_ = 0; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_VARINT_HPACK_VARINT_DECODER_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_decoder_test.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_decoder_test.cc new file mode 100644 index 00000000000..020ca0f7a6a --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_decoder_test.cc @@ -0,0 +1,309 @@ +// Copyright 2018 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/hpack/varint/hpack_varint_decoder.h" + +// Test HpackVarintDecoder against hardcoded data. + +#include <stddef.h> + +#include "absl/base/macros.h" +#include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" +#include "quiche/http2/platform/api/http2_logging.h" +#include "quiche/http2/tools/random_decoder_test.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/common/platform/api/quiche_test_helpers.h" + +using ::testing::AssertionSuccess; + +namespace http2 { +namespace test { +namespace { + +class HpackVarintDecoderTest + : public RandomDecoderTest, + public ::testing::WithParamInterface<std::tuple<uint8_t, const char*>> { + protected: + HpackVarintDecoderTest() + : high_bits_(std::get<0>(GetParam())), + suffix_(absl::HexStringToBytes(std::get<1>(GetParam()))), + prefix_length_(0) {} + + void DecodeExpectSuccess(absl::string_view data, uint32_t prefix_length, + uint64_t expected_value) { + Validator validator = [expected_value, this]( + const DecodeBuffer& /*db*/, + DecodeStatus /*status*/) -> AssertionResult { + VERIFY_EQ(expected_value, decoder_.value()) + << "Value doesn't match expected: " << decoder_.value() + << " != " << expected_value; + return AssertionSuccess(); + }; + + // First validate that decoding is done and that we've advanced the cursor + // the expected amount. + validator = ValidateDoneAndOffset(/* offset = */ data.size(), validator); + + EXPECT_TRUE(Decode(data, prefix_length, validator)); + + EXPECT_EQ(expected_value, decoder_.value()); + } + + void DecodeExpectError(absl::string_view data, uint32_t prefix_length) { + Validator validator = [](const DecodeBuffer& /*db*/, + DecodeStatus status) -> AssertionResult { + VERIFY_EQ(DecodeStatus::kDecodeError, status); + return AssertionSuccess(); + }; + + EXPECT_TRUE(Decode(data, prefix_length, validator)); + } + + private: + AssertionResult Decode(absl::string_view data, uint32_t prefix_length, + const Validator validator) { + prefix_length_ = prefix_length; + + // Copy |data| so that it can be modified. + std::string data_copy(data); + + // Bits of the first byte not part of the prefix should be ignored. + uint8_t high_bits_mask = 0b11111111 << prefix_length_; + data_copy[0] |= (high_bits_mask & high_bits_); + + // Extra bytes appended to the input should be ignored. + data_copy.append(suffix_); + + DecodeBuffer b(data_copy); + + // StartDecoding, above, requires the DecodeBuffer be non-empty so that it + // can call Start with the prefix byte. + bool return_non_zero_on_first = true; + + return DecodeAndValidateSeveralWays(&b, return_non_zero_on_first, + validator); + } + + DecodeStatus StartDecoding(DecodeBuffer* b) override { + QUICHE_CHECK_LT(0u, b->Remaining()); + uint8_t prefix = b->DecodeUInt8(); + return decoder_.Start(prefix, prefix_length_, b); + } + + DecodeStatus ResumeDecoding(DecodeBuffer* b) override { + return decoder_.Resume(b); + } + + // Bits of the first byte not part of the prefix. + const uint8_t high_bits_; + // Extra bytes appended to the input. + const std::string suffix_; + + HpackVarintDecoder decoder_; + uint8_t prefix_length_; +}; + +INSTANTIATE_TEST_SUITE_P( + HpackVarintDecoderTest, HpackVarintDecoderTest, + ::testing::Combine( + // Bits of the first byte not part of the prefix should be ignored. + ::testing::Values(0b00000000, 0b11111111, 0b10101010), + // Extra bytes appended to the input should be ignored. + ::testing::Values("", "00", "666f6f"))); + +struct { + const char* data; + uint32_t prefix_length; + uint64_t expected_value; +} kSuccessTestData[] = { + // Zero value with different prefix lengths. + {"00", 3, 0}, + {"00", 4, 0}, + {"00", 5, 0}, + {"00", 6, 0}, + {"00", 7, 0}, + {"00", 8, 0}, + // Small values that fit in the prefix. + {"06", 3, 6}, + {"0d", 4, 13}, + {"10", 5, 16}, + {"29", 6, 41}, + {"56", 7, 86}, + {"bf", 8, 191}, + // Values of 2^n-1, which have an all-zero extension byte. + {"0700", 3, 7}, + {"0f00", 4, 15}, + {"1f00", 5, 31}, + {"3f00", 6, 63}, + {"7f00", 7, 127}, + {"ff00", 8, 255}, + // Values of 2^n-1, plus one extra byte of padding. + {"078000", 3, 7}, + {"0f8000", 4, 15}, + {"1f8000", 5, 31}, + {"3f8000", 6, 63}, + {"7f8000", 7, 127}, + {"ff8000", 8, 255}, + // Values requiring one extension byte. + {"0760", 3, 103}, + {"0f2a", 4, 57}, + {"1f7f", 5, 158}, + {"3f02", 6, 65}, + {"7f49", 7, 200}, + {"ff6f", 8, 366}, + // Values requiring one extension byte, plus one byte of padding. + {"07e000", 3, 103}, + {"0faa00", 4, 57}, + {"1fff00", 5, 158}, + {"3f8200", 6, 65}, + {"7fc900", 7, 200}, + {"ffef00", 8, 366}, + // Values requiring one extension byte, plus two bytes of padding. + {"07e08000", 3, 103}, + {"0faa8000", 4, 57}, + {"1fff8000", 5, 158}, + {"3f828000", 6, 65}, + {"7fc98000", 7, 200}, + {"ffef8000", 8, 366}, + // Values requiring one extension byte, plus the maximum amount of padding. + {"07e0808080808080808000", 3, 103}, + {"0faa808080808080808000", 4, 57}, + {"1fff808080808080808000", 5, 158}, + {"3f82808080808080808000", 6, 65}, + {"7fc9808080808080808000", 7, 200}, + {"ffef808080808080808000", 8, 366}, + // Values requiring two extension bytes. + {"07b260", 3, 12345}, + {"0f8a2a", 4, 5401}, + {"1fa87f", 5, 16327}, + {"3fd002", 6, 399}, + {"7fff49", 7, 9598}, + {"ffe32f", 8, 6370}, + // Values requiring two extension bytes, plus one byte of padding. + {"07b2e000", 3, 12345}, + {"0f8aaa00", 4, 5401}, + {"1fa8ff00", 5, 16327}, + {"3fd08200", 6, 399}, + {"7fffc900", 7, 9598}, + {"ffe3af00", 8, 6370}, + // Values requiring two extension bytes, plus the maximum amount of padding. + {"07b2e080808080808000", 3, 12345}, + {"0f8aaa80808080808000", 4, 5401}, + {"1fa8ff80808080808000", 5, 16327}, + {"3fd08280808080808000", 6, 399}, + {"7fffc980808080808000", 7, 9598}, + {"ffe3af80808080808000", 8, 6370}, + // Values requiring three extension bytes. + {"078ab260", 3, 1579281}, + {"0fc18a2a", 4, 689488}, + {"1fada87f", 5, 2085964}, + {"3fa0d002", 6, 43103}, + {"7ffeff49", 7, 1212541}, + {"ff93de23", 8, 585746}, + // Values requiring three extension bytes, plus one byte of padding. + {"078ab2e000", 3, 1579281}, + {"0fc18aaa00", 4, 689488}, + {"1fada8ff00", 5, 2085964}, + {"3fa0d08200", 6, 43103}, + {"7ffeffc900", 7, 1212541}, + {"ff93dea300", 8, 585746}, + // Values requiring four extension bytes. + {"079f8ab260", 3, 202147110}, + {"0fa2c18a2a", 4, 88252593}, + {"1fd0ada87f", 5, 266999535}, + {"3ff9a0d002", 6, 5509304}, + {"7f9efeff49", 7, 155189149}, + {"ffaa82f404", 8, 10289705}, + // Values requiring four extension bytes, plus one byte of padding. + {"079f8ab2e000", 3, 202147110}, + {"0fa2c18aaa00", 4, 88252593}, + {"1fd0ada8ff00", 5, 266999535}, + {"3ff9a0d08200", 6, 5509304}, + {"7f9efeffc900", 7, 155189149}, + {"ffaa82f48400", 8, 10289705}, + // Values requiring six extension bytes. + {"0783aa9f8ab260", 3, 3311978140938}, + {"0ff0b0a2c18a2a", 4, 1445930244223}, + {"1fda84d0ada87f", 5, 4374519874169}, + {"3fb5fbf9a0d002", 6, 90263420404}, + {"7fcff19efeff49", 7, 2542616951118}, + {"ff9fa486bbc327", 8, 1358138807070}, + // Values requiring eight extension bytes. + {"07f19883aa9f8ab260", 3, 54263449861016696}, + {"0f84fdf0b0a2c18a2a", 4, 23690121121119891}, + {"1fa0dfda84d0ada87f", 5, 71672133617889215}, + {"3f9ff0b5fbf9a0d002", 6, 1478875878881374}, + {"7ffbc1cff19efeff49", 7, 41658236125045114}, + {"ff91b6fb85af99c342", 8, 37450237664484368}, + // Values requiring ten extension bytes. + {"0794f1f19883aa9f8ab201", 3, 12832019021693745307u}, + {"0fa08f84fdf0b0a2c18a01", 4, 9980690937382242223u}, + {"1fbfdda0dfda84d0ada801", 5, 12131360551794650846u}, + {"3f9dc79ff0b5fbf9a0d001", 6, 15006530362736632796u}, + {"7f8790fbc1cff19efeff01", 7, 18445754019193211014u}, + {"fffba8c5b8d3fe9f8c8401", 8, 9518498503615141242u}, + // Maximum value: 2^64-1. + {"07f8ffffffffffffffff01", 3, 18446744073709551615u}, + {"0ff0ffffffffffffffff01", 4, 18446744073709551615u}, + {"1fe0ffffffffffffffff01", 5, 18446744073709551615u}, + {"3fc0ffffffffffffffff01", 6, 18446744073709551615u}, + {"7f80ffffffffffffffff01", 7, 18446744073709551615u}, + {"ff80feffffffffffffff01", 8, 18446744073709551615u}, + // Examples from RFC7541 C.1. + {"0a", 5, 10}, + {"1f9a0a", 5, 1337}, +}; + +TEST_P(HpackVarintDecoderTest, Success) { + for (size_t i = 0; i < ABSL_ARRAYSIZE(kSuccessTestData); ++i) { + DecodeExpectSuccess(absl::HexStringToBytes(kSuccessTestData[i].data), + kSuccessTestData[i].prefix_length, + kSuccessTestData[i].expected_value); + } +} + +struct { + const char* data; + uint32_t prefix_length; +} kErrorTestData[] = { + // Too many extension bytes, all 0s (except for extension bit in each byte). + {"0780808080808080808080", 3}, + {"0f80808080808080808080", 4}, + {"1f80808080808080808080", 5}, + {"3f80808080808080808080", 6}, + {"7f80808080808080808080", 7}, + {"ff80808080808080808080", 8}, + // Too many extension bytes, all 1s. + {"07ffffffffffffffffffff", 3}, + {"0fffffffffffffffffffff", 4}, + {"1fffffffffffffffffffff", 5}, + {"3fffffffffffffffffffff", 6}, + {"7fffffffffffffffffffff", 7}, + {"ffffffffffffffffffffff", 8}, + // Value of 2^64, one higher than maximum of 2^64-1. + {"07f9ffffffffffffffff01", 3}, + {"0ff1ffffffffffffffff01", 4}, + {"1fe1ffffffffffffffff01", 5}, + {"3fc1ffffffffffffffff01", 6}, + {"7f81ffffffffffffffff01", 7}, + {"ff81feffffffffffffff01", 8}, + // Maximum value: 2^64-1, with one byte of padding. + {"07f8ffffffffffffffff8100", 3}, + {"0ff0ffffffffffffffff8100", 4}, + {"1fe0ffffffffffffffff8100", 5}, + {"3fc0ffffffffffffffff8100", 6}, + {"7f80ffffffffffffffff8100", 7}, + {"ff80feffffffffffffff8100", 8}}; + +TEST_P(HpackVarintDecoderTest, Error) { + for (size_t i = 0; i < ABSL_ARRAYSIZE(kErrorTestData); ++i) { + DecodeExpectError(absl::HexStringToBytes(kErrorTestData[i].data), + kErrorTestData[i].prefix_length); + } +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_encoder.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_encoder.cc new file mode 100644 index 00000000000..89beb4ae00b --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_encoder.cc @@ -0,0 +1,47 @@ +// 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/hpack/varint/hpack_varint_encoder.h" + +#include <limits> + +#include "quiche/http2/platform/api/http2_logging.h" + +namespace http2 { + +// static +void HpackVarintEncoder::Encode(uint8_t high_bits, uint8_t prefix_length, + uint64_t varint, std::string* output) { + QUICHE_DCHECK_LE(1u, prefix_length); + QUICHE_DCHECK_LE(prefix_length, 8u); + + // prefix_mask defines the sequence of low-order bits of the first byte + // that encode the prefix of the value. It is also the marker in those bits + // of the first byte indicating that at least one extension byte is needed. + const uint8_t prefix_mask = (1 << prefix_length) - 1; + QUICHE_DCHECK_EQ(0, high_bits & prefix_mask); + + if (varint < prefix_mask) { + // The integer fits into the prefix in its entirety. + unsigned char first_byte = high_bits | static_cast<unsigned char>(varint); + output->push_back(first_byte); + return; + } + + // Extension bytes are needed. + unsigned char first_byte = high_bits | prefix_mask; + output->push_back(first_byte); + + varint -= prefix_mask; + while (varint >= 128) { + // Encode the next seven bits, with continuation bit set to one. + output->push_back(0b10000000 | (varint % 128)); + varint >>= 7; + } + + // Encode final seven bits, with continuation bit set to zero. + output->push_back(varint); +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_encoder.h b/chromium/net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_encoder.h new file mode 100644 index 00000000000..69acc161a67 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_encoder.h @@ -0,0 +1,29 @@ +// 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_HPACK_VARINT_HPACK_VARINT_ENCODER_H_ +#define QUICHE_HTTP2_HPACK_VARINT_HPACK_VARINT_ENCODER_H_ + +#include <cstddef> +#include <cstdint> +#include <string> + +#include "quiche/common/platform/api/quiche_export.h" + +namespace http2 { + +// HPACK integer encoder class with single static method implementing variable +// length integer representation defined in RFC7541, Section 5.1: +// https://httpwg.org/specs/rfc7541.html#integer.representation +class QUICHE_EXPORT_PRIVATE HpackVarintEncoder { + public: + // Encode |varint|, appending encoded data to |*output|. + // Appends between 1 and 11 bytes in total. + static void Encode(uint8_t high_bits, uint8_t prefix_length, uint64_t varint, + std::string* output); +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_VARINT_HPACK_VARINT_ENCODER_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_encoder_test.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_encoder_test.cc new file mode 100644 index 00000000000..bd51606b079 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_encoder_test.cc @@ -0,0 +1,161 @@ +// 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/hpack/varint/hpack_varint_encoder.h" + +#include "absl/base/macros.h" +#include "absl/strings/escaping.h" +#include "quiche/common/platform/api/quiche_test.h" + +namespace http2 { +namespace test { +namespace { + +struct { + uint8_t high_bits; + uint8_t prefix_length; + uint64_t value; + uint8_t expected_encoding; +} kShortTestData[] = {{0b10110010, 1, 0, 0b10110010}, + {0b10101100, 2, 2, 0b10101110}, + {0b10100000, 3, 6, 0b10100110}, + {0b10110000, 4, 13, 0b10111101}, + {0b10100000, 5, 8, 0b10101000}, + {0b11000000, 6, 48, 0b11110000}, + {0b10000000, 7, 99, 0b11100011}, + // Example from RFC7541 C.1. + {0b00000000, 5, 10, 0b00001010}}; + +// Encode integers that fit in the prefix. +TEST(HpackVarintEncoderTest, Short) { + for (size_t i = 0; i < ABSL_ARRAYSIZE(kShortTestData); ++i) { + std::string output; + HpackVarintEncoder::Encode(kShortTestData[i].high_bits, + kShortTestData[i].prefix_length, + kShortTestData[i].value, &output); + ASSERT_EQ(1u, output.size()); + EXPECT_EQ(kShortTestData[i].expected_encoding, + static_cast<uint8_t>(output[0])); + } +} + +struct { + uint8_t high_bits; + uint8_t prefix_length; + uint64_t value; + const char* expected_encoding; +} kLongTestData[] = { + // One extension byte. + {0b10011000, 3, 103, "9f60"}, + {0b10010000, 4, 57, "9f2a"}, + {0b11000000, 5, 158, "df7f"}, + {0b01000000, 6, 65, "7f02"}, + {0b00000000, 7, 200, "7f49"}, + // Two extension bytes. + {0b10011000, 3, 12345, "9fb260"}, + {0b10010000, 4, 5401, "9f8a2a"}, + {0b11000000, 5, 16327, "dfa87f"}, + {0b01000000, 6, 399, "7fd002"}, + {0b00000000, 7, 9598, "7fff49"}, + // Three extension bytes. + {0b10011000, 3, 1579281, "9f8ab260"}, + {0b10010000, 4, 689488, "9fc18a2a"}, + {0b11000000, 5, 2085964, "dfada87f"}, + {0b01000000, 6, 43103, "7fa0d002"}, + {0b00000000, 7, 1212541, "7ffeff49"}, + // Four extension bytes. + {0b10011000, 3, 202147110, "9f9f8ab260"}, + {0b10010000, 4, 88252593, "9fa2c18a2a"}, + {0b11000000, 5, 266999535, "dfd0ada87f"}, + {0b01000000, 6, 5509304, "7ff9a0d002"}, + {0b00000000, 7, 155189149, "7f9efeff49"}, + // Six extension bytes. + {0b10011000, 3, 3311978140938, "9f83aa9f8ab260"}, + {0b10010000, 4, 1445930244223, "9ff0b0a2c18a2a"}, + {0b11000000, 5, 4374519874169, "dfda84d0ada87f"}, + {0b01000000, 6, 90263420404, "7fb5fbf9a0d002"}, + {0b00000000, 7, 2542616951118, "7fcff19efeff49"}, + // Eight extension bytes. + {0b10011000, 3, 54263449861016696, "9ff19883aa9f8ab260"}, + {0b10010000, 4, 23690121121119891, "9f84fdf0b0a2c18a2a"}, + {0b11000000, 5, 71672133617889215, "dfa0dfda84d0ada87f"}, + {0b01000000, 6, 1478875878881374, "7f9ff0b5fbf9a0d002"}, + {0b00000000, 7, 41658236125045114, "7ffbc1cff19efeff49"}, + // Ten extension bytes. + {0b10011000, 3, 12832019021693745307u, "9f94f1f19883aa9f8ab201"}, + {0b10010000, 4, 9980690937382242223u, "9fa08f84fdf0b0a2c18a01"}, + {0b11000000, 5, 12131360551794650846u, "dfbfdda0dfda84d0ada801"}, + {0b01000000, 6, 15006530362736632796u, "7f9dc79ff0b5fbf9a0d001"}, + {0b00000000, 7, 18445754019193211014u, "7f8790fbc1cff19efeff01"}, + // Maximum value: 2^64-1. + {0b10011000, 3, 18446744073709551615u, "9ff8ffffffffffffffff01"}, + {0b10010000, 4, 18446744073709551615u, "9ff0ffffffffffffffff01"}, + {0b11000000, 5, 18446744073709551615u, "dfe0ffffffffffffffff01"}, + {0b01000000, 6, 18446744073709551615u, "7fc0ffffffffffffffff01"}, + {0b00000000, 7, 18446744073709551615u, "7f80ffffffffffffffff01"}, + // Example from RFC7541 C.1. + {0b00000000, 5, 1337, "1f9a0a"}, +}; + +// Encode integers that do not fit in the prefix. +TEST(HpackVarintEncoderTest, Long) { + // Test encoding byte by byte, also test encoding in + // a single ResumeEncoding() call. + for (size_t i = 0; i < ABSL_ARRAYSIZE(kLongTestData); ++i) { + std::string expected_encoding = + absl::HexStringToBytes(kLongTestData[i].expected_encoding); + + std::string output; + HpackVarintEncoder::Encode(kLongTestData[i].high_bits, + kLongTestData[i].prefix_length, + kLongTestData[i].value, &output); + + EXPECT_EQ(expected_encoding, output); + } +} + +struct { + uint8_t high_bits; + uint8_t prefix_length; + uint64_t value; + uint8_t expected_encoding_first_byte; +} kLastByteIsZeroTestData[] = { + {0b10110010, 1, 1, 0b10110011}, {0b10101100, 2, 3, 0b10101111}, + {0b10101000, 3, 7, 0b10101111}, {0b10110000, 4, 15, 0b10111111}, + {0b10100000, 5, 31, 0b10111111}, {0b11000000, 6, 63, 0b11111111}, + {0b10000000, 7, 127, 0b11111111}, {0b00000000, 8, 255, 0b11111111}}; + +// Make sure that the encoder outputs the last byte even when it is zero. This +// happens exactly when encoding the value 2^prefix_length - 1. +TEST(HpackVarintEncoderTest, LastByteIsZero) { + for (size_t i = 0; i < ABSL_ARRAYSIZE(kLastByteIsZeroTestData); ++i) { + std::string output; + HpackVarintEncoder::Encode(kLastByteIsZeroTestData[i].high_bits, + kLastByteIsZeroTestData[i].prefix_length, + kLastByteIsZeroTestData[i].value, &output); + ASSERT_EQ(2u, output.size()); + EXPECT_EQ(kLastByteIsZeroTestData[i].expected_encoding_first_byte, + static_cast<uint8_t>(output[0])); + EXPECT_EQ(0b00000000, output[1]); + } +} + +// Test that encoder appends correctly to non-empty string. +TEST(HpackVarintEncoderTest, Append) { + std::string output("foo"); + EXPECT_EQ(absl::HexStringToBytes("666f6f"), output); + + HpackVarintEncoder::Encode(0b10011000, 3, 103, &output); + EXPECT_EQ(absl::HexStringToBytes("666f6f9f60"), output); + + HpackVarintEncoder::Encode(0b10100000, 5, 8, &output); + EXPECT_EQ(absl::HexStringToBytes("666f6f9f60a8"), output); + + HpackVarintEncoder::Encode(0b10011000, 3, 202147110, &output); + EXPECT_EQ(absl::HexStringToBytes("666f6f9f60a89f9f8ab260"), output); +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_round_trip_test.cc b/chromium/net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_round_trip_test.cc new file mode 100644 index 00000000000..ba5164961aa --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_round_trip_test.cc @@ -0,0 +1,416 @@ +// 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/hpack/varint/hpack_varint_decoder.h" + +// Test HpackVarintDecoder against data encoded via HpackBlockBuilder, +// which uses HpackVarintEncoder under the hood. + +#include <stddef.h> + +#include <iterator> +#include <set> +#include <vector> + +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "quiche/http2/hpack/tools/hpack_block_builder.h" +#include "quiche/http2/platform/api/http2_logging.h" +#include "quiche/http2/tools/random_decoder_test.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/common/quiche_text_utils.h" + +using ::testing::AssertionFailure; +using ::testing::AssertionSuccess; + +namespace http2 { +namespace test { +namespace { + +// Returns the highest value with the specified number of extension bytes +// and the specified prefix length (bits). +uint64_t HiValueOfExtensionBytes(uint32_t extension_bytes, + uint32_t prefix_length) { + return (1 << prefix_length) - 2 + + (extension_bytes == 0 ? 0 : (1LLU << (extension_bytes * 7))); +} + +class HpackVarintRoundTripTest : public RandomDecoderTest { + protected: + HpackVarintRoundTripTest() : prefix_length_(0) {} + + DecodeStatus StartDecoding(DecodeBuffer* b) override { + QUICHE_CHECK_LT(0u, b->Remaining()); + uint8_t prefix = b->DecodeUInt8(); + return decoder_.Start(prefix, prefix_length_, b); + } + + DecodeStatus ResumeDecoding(DecodeBuffer* b) override { + return decoder_.Resume(b); + } + + void DecodeSeveralWays(uint64_t expected_value, uint32_t expected_offset) { + // The validator is called after each of the several times that the input + // DecodeBuffer is decoded, each with a different segmentation of the input. + // Validate that decoder_.value() matches the expected value. + Validator validator = [expected_value, this]( + const DecodeBuffer& /*db*/, + DecodeStatus /*status*/) -> AssertionResult { + if (decoder_.value() != expected_value) { + return AssertionFailure() + << "Value doesn't match expected: " << decoder_.value() + << " != " << expected_value; + } + return AssertionSuccess(); + }; + + // First validate that decoding is done and that we've advanced the cursor + // the expected amount. + validator = ValidateDoneAndOffset(expected_offset, validator); + + // StartDecoding, above, requires the DecodeBuffer be non-empty so that it + // can call Start with the prefix byte. + bool return_non_zero_on_first = true; + + DecodeBuffer b(buffer_); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, return_non_zero_on_first, validator)); + + EXPECT_EQ(expected_value, decoder_.value()); + EXPECT_EQ(expected_offset, b.Offset()); + } + + void EncodeNoRandom(uint64_t value, uint8_t prefix_length) { + QUICHE_DCHECK_LE(3, prefix_length); + QUICHE_DCHECK_LE(prefix_length, 8); + prefix_length_ = prefix_length; + + HpackBlockBuilder bb; + bb.AppendHighBitsAndVarint(0, prefix_length_, value); + buffer_ = bb.buffer(); + ASSERT_LT(0u, buffer_.size()); + + const uint8_t prefix_mask = (1 << prefix_length_) - 1; + ASSERT_EQ(static_cast<uint8_t>(buffer_[0]), + static_cast<uint8_t>(buffer_[0]) & prefix_mask); + } + + void Encode(uint64_t value, uint8_t prefix_length) { + EncodeNoRandom(value, prefix_length); + // Add some random bits to the prefix (the first byte) above the mask. + uint8_t prefix = buffer_[0]; + buffer_[0] = prefix | (Random().Rand8() << prefix_length); + const uint8_t prefix_mask = (1 << prefix_length_) - 1; + ASSERT_EQ(prefix, buffer_[0] & prefix_mask); + } + + // This is really a test of HpackBlockBuilder, making sure that the input to + // HpackVarintDecoder is as expected, which also acts as confirmation that + // my thinking about the encodings being used by the tests, i.e. cover the + // range desired. + void ValidateEncoding(uint64_t value, uint64_t minimum, uint64_t maximum, + size_t expected_bytes) { + ASSERT_EQ(expected_bytes, buffer_.size()); + if (expected_bytes > 1) { + const uint8_t prefix_mask = (1 << prefix_length_) - 1; + EXPECT_EQ(prefix_mask, buffer_[0] & prefix_mask); + size_t last = expected_bytes - 1; + for (size_t ndx = 1; ndx < last; ++ndx) { + // Before the last extension byte, we expect the high-bit set. + uint8_t byte = buffer_[ndx]; + if (value == minimum) { + EXPECT_EQ(0x80, byte) << "ndx=" << ndx; + } else if (value == maximum) { + if (expected_bytes < 11) { + EXPECT_EQ(0xff, byte) << "ndx=" << ndx; + } + } else { + EXPECT_EQ(0x80, byte & 0x80) << "ndx=" << ndx; + } + } + // The last extension byte should not have the high-bit set. + uint8_t byte = buffer_[last]; + if (value == minimum) { + if (expected_bytes == 2) { + EXPECT_EQ(0x00, byte); + } else { + EXPECT_EQ(0x01, byte); + } + } else if (value == maximum) { + if (expected_bytes < 11) { + EXPECT_EQ(0x7f, byte); + } + } else { + EXPECT_EQ(0x00, byte & 0x80); + } + } else { + const uint8_t prefix_mask = (1 << prefix_length_) - 1; + EXPECT_EQ(value, static_cast<uint32_t>(buffer_[0] & prefix_mask)); + EXPECT_LT(value, prefix_mask); + } + } + + void EncodeAndDecodeValues(const std::set<uint64_t>& values, + uint8_t prefix_length, size_t expected_bytes) { + QUICHE_CHECK(!values.empty()); + const uint64_t minimum = *values.begin(); + const uint64_t maximum = *values.rbegin(); + for (const uint64_t value : values) { + Encode(value, prefix_length); // Sets buffer_. + + std::string msg = absl::StrCat("value=", value, " (0x", absl::Hex(value), + "), prefix_length=", prefix_length, + ", expected_bytes=", expected_bytes, "\n", + quiche::QuicheTextUtils::HexDump(buffer_)); + + if (value == minimum) { + HTTP2_LOG(INFO) << "Checking minimum; " << msg; + } else if (value == maximum) { + HTTP2_LOG(INFO) << "Checking maximum; " << msg; + } + + SCOPED_TRACE(msg); + ValidateEncoding(value, minimum, maximum, expected_bytes); + DecodeSeveralWays(value, expected_bytes); + + // Append some random data to the end of buffer_ and repeat. That random + // data should be ignored. + buffer_.append(Random().RandString(1 + Random().Uniform(10))); + DecodeSeveralWays(value, expected_bytes); + + // If possible, add extension bytes that don't change the value. + if (1 < expected_bytes) { + buffer_.resize(expected_bytes); + for (uint8_t total_bytes = expected_bytes + 1; total_bytes <= 6; + ++total_bytes) { + // Mark the current last byte as not being the last one. + EXPECT_EQ(0x00, 0x80 & buffer_.back()); + buffer_.back() |= 0x80; + buffer_.push_back('\0'); + DecodeSeveralWays(value, total_bytes); + } + } + } + } + + // Encode values (all or some of it) in [start, start+range). Check + // that |start| is the smallest value and |start+range-1| is the largest value + // corresponding to |expected_bytes|, except if |expected_bytes| is maximal. + void EncodeAndDecodeValuesInRange(uint64_t start, uint64_t range, + uint8_t prefix_length, + size_t expected_bytes) { + const uint8_t prefix_mask = (1 << prefix_length) - 1; + const uint64_t beyond = start + range; + + HTTP2_LOG(INFO) + << "############################################################"; + HTTP2_LOG(INFO) << "prefix_length=" << static_cast<int>(prefix_length); + HTTP2_LOG(INFO) << "prefix_mask=" << std::hex + << static_cast<int>(prefix_mask); + HTTP2_LOG(INFO) << "start=" << start << " (" << std::hex << start << ")"; + HTTP2_LOG(INFO) << "range=" << range << " (" << std::hex << range << ")"; + HTTP2_LOG(INFO) << "beyond=" << beyond << " (" << std::hex << beyond << ")"; + HTTP2_LOG(INFO) << "expected_bytes=" << expected_bytes; + + if (expected_bytes < 11) { + // Confirm the claim that beyond requires more bytes. + Encode(beyond, prefix_length); + EXPECT_EQ(expected_bytes + 1, buffer_.size()) + << quiche::QuicheTextUtils::HexDump(buffer_); + } + + std::set<uint64_t> values; + if (range < 200) { + // Select all values in the range. + for (uint64_t offset = 0; offset < range; ++offset) { + values.insert(start + offset); + } + } else { + // Select some values in this range, including the minimum and maximum + // values that require exactly |expected_bytes| extension bytes. + values.insert({start, start + 1, beyond - 2, beyond - 1}); + while (values.size() < 100) { + values.insert(Random().UniformInRange(start, beyond - 1)); + } + } + + EncodeAndDecodeValues(values, prefix_length, expected_bytes); + } + + HpackVarintDecoder decoder_; + std::string buffer_; + uint8_t prefix_length_; +}; + +// To help me and future debuggers of varint encodings, this HTTP2_LOGs out the +// transition points where a new extension byte is added. +TEST_F(HpackVarintRoundTripTest, Encode) { + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint64_t a = HiValueOfExtensionBytes(0, prefix_length); + const uint64_t b = HiValueOfExtensionBytes(1, prefix_length); + const uint64_t c = HiValueOfExtensionBytes(2, prefix_length); + const uint64_t d = HiValueOfExtensionBytes(3, prefix_length); + const uint64_t e = HiValueOfExtensionBytes(4, prefix_length); + const uint64_t f = HiValueOfExtensionBytes(5, prefix_length); + const uint64_t g = HiValueOfExtensionBytes(6, prefix_length); + const uint64_t h = HiValueOfExtensionBytes(7, prefix_length); + const uint64_t i = HiValueOfExtensionBytes(8, prefix_length); + const uint64_t j = HiValueOfExtensionBytes(9, prefix_length); + + HTTP2_LOG(INFO) + << "############################################################"; + HTTP2_LOG(INFO) << "prefix_length=" << prefix_length << " a=" << a + << " b=" << b << " c=" << c << " d=" << d + << " e=" << e << " f=" << f << " g=" << g + << " h=" << h << " i=" << i << " j=" << j; + + std::vector<uint64_t> values = { + 0, 1, // Force line break. + a - 1, a, a + 1, a + 2, a + 3, // Force line break. + b - 1, b, b + 1, b + 2, b + 3, // Force line break. + c - 1, c, c + 1, c + 2, c + 3, // Force line break. + d - 1, d, d + 1, d + 2, d + 3, // Force line break. + e - 1, e, e + 1, e + 2, e + 3, // Force line break. + f - 1, f, f + 1, f + 2, f + 3, // Force line break. + g - 1, g, g + 1, g + 2, g + 3, // Force line break. + h - 1, h, h + 1, h + 2, h + 3, // Force line break. + i - 1, i, i + 1, i + 2, i + 3, // Force line break. + j - 1, j, j + 1, j + 2, j + 3, // Force line break. + }; + + for (uint64_t value : values) { + EncodeNoRandom(value, prefix_length); + std::string dump = quiche::QuicheTextUtils::HexDump(buffer_); + HTTP2_LOG(INFO) << absl::StrFormat("%10llu %0#18x ", value, value) + << quiche::QuicheTextUtils::HexDump(buffer_).substr(7); + } + } +} + +TEST_F(HpackVarintRoundTripTest, FromSpec1337) { + DecodeBuffer b(absl::string_view("\x1f\x9a\x0a")); + uint32_t prefix_length = 5; + uint8_t p = b.DecodeUInt8(); + EXPECT_EQ(1u, b.Offset()); + EXPECT_EQ(DecodeStatus::kDecodeDone, decoder_.Start(p, prefix_length, &b)); + EXPECT_EQ(3u, b.Offset()); + EXPECT_EQ(1337u, decoder_.value()); + + EncodeNoRandom(1337, prefix_length); + EXPECT_EQ(3u, buffer_.size()); + EXPECT_EQ('\x1f', buffer_[0]); + EXPECT_EQ('\x9a', buffer_[1]); + EXPECT_EQ('\x0a', buffer_[2]); +} + +// Test all the values that fit into the prefix (one less than the mask). +TEST_F(HpackVarintRoundTripTest, ValidatePrefixOnly) { + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint8_t prefix_mask = (1 << prefix_length) - 1; + EncodeAndDecodeValuesInRange(0, prefix_mask, prefix_length, 1); + } +} + +// Test all values that require exactly 1 extension byte. +TEST_F(HpackVarintRoundTripTest, ValidateOneExtensionByte) { + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint64_t start = HiValueOfExtensionBytes(0, prefix_length) + 1; + EncodeAndDecodeValuesInRange(start, 128, prefix_length, 2); + } +} + +// Test *some* values that require exactly 2 extension bytes. +TEST_F(HpackVarintRoundTripTest, ValidateTwoExtensionBytes) { + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint64_t start = HiValueOfExtensionBytes(1, prefix_length) + 1; + const uint64_t range = 127 << 7; + + EncodeAndDecodeValuesInRange(start, range, prefix_length, 3); + } +} + +// Test *some* values that require 3 extension bytes. +TEST_F(HpackVarintRoundTripTest, ValidateThreeExtensionBytes) { + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint64_t start = HiValueOfExtensionBytes(2, prefix_length) + 1; + const uint64_t range = 127 << 14; + + EncodeAndDecodeValuesInRange(start, range, prefix_length, 4); + } +} + +// Test *some* values that require 4 extension bytes. +TEST_F(HpackVarintRoundTripTest, ValidateFourExtensionBytes) { + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint64_t start = HiValueOfExtensionBytes(3, prefix_length) + 1; + const uint64_t range = 127 << 21; + + EncodeAndDecodeValuesInRange(start, range, prefix_length, 5); + } +} + +// Test *some* values that require 5 extension bytes. +TEST_F(HpackVarintRoundTripTest, ValidateFiveExtensionBytes) { + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint64_t start = HiValueOfExtensionBytes(4, prefix_length) + 1; + const uint64_t range = 127llu << 28; + + EncodeAndDecodeValuesInRange(start, range, prefix_length, 6); + } +} + +// Test *some* values that require 6 extension bytes. +TEST_F(HpackVarintRoundTripTest, ValidateSixExtensionBytes) { + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint64_t start = HiValueOfExtensionBytes(5, prefix_length) + 1; + const uint64_t range = 127llu << 35; + + EncodeAndDecodeValuesInRange(start, range, prefix_length, 7); + } +} + +// Test *some* values that require 7 extension bytes. +TEST_F(HpackVarintRoundTripTest, ValidateSevenExtensionBytes) { + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint64_t start = HiValueOfExtensionBytes(6, prefix_length) + 1; + const uint64_t range = 127llu << 42; + + EncodeAndDecodeValuesInRange(start, range, prefix_length, 8); + } +} + +// Test *some* values that require 8 extension bytes. +TEST_F(HpackVarintRoundTripTest, ValidateEightExtensionBytes) { + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint64_t start = HiValueOfExtensionBytes(7, prefix_length) + 1; + const uint64_t range = 127llu << 49; + + EncodeAndDecodeValuesInRange(start, range, prefix_length, 9); + } +} + +// Test *some* values that require 9 extension bytes. +TEST_F(HpackVarintRoundTripTest, ValidateNineExtensionBytes) { + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint64_t start = HiValueOfExtensionBytes(8, prefix_length) + 1; + const uint64_t range = 127llu << 56; + + EncodeAndDecodeValuesInRange(start, range, prefix_length, 10); + } +} + +// Test *some* values that require 10 extension bytes. +TEST_F(HpackVarintRoundTripTest, ValidateTenExtensionBytes) { + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint64_t start = HiValueOfExtensionBytes(9, prefix_length) + 1; + const uint64_t range = std::numeric_limits<uint64_t>::max() - start; + + EncodeAndDecodeValuesInRange(start, range, prefix_length, 11); + } +} + +} // namespace +} // namespace test +} // namespace http2 |