Project import generated by Copybara. PiperOrigin-RevId: 224614037 Change-Id: I14e53449d4aeccb328f86828c76b5f09dea0d4b8
diff --git a/http2/decoder/decode_buffer.cc b/http2/decoder/decode_buffer.cc new file mode 100644 index 0000000..b24420a --- /dev/null +++ b/http2/decoder/decode_buffer.cc
@@ -0,0 +1,93 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" + +namespace http2 { + +uint8_t DecodeBuffer::DecodeUInt8() { + return static_cast<uint8_t>(DecodeChar()); +} + +uint16_t DecodeBuffer::DecodeUInt16() { + DCHECK_LE(2u, Remaining()); + const uint8_t b1 = DecodeUInt8(); + const uint8_t b2 = DecodeUInt8(); + // Note that chars are automatically promoted to ints during arithmetic, + // so the b1 << 8 doesn't end up as zero before being or-ed with b2. + // And the left-shift operator has higher precedence than the or operator. + return b1 << 8 | b2; +} + +uint32_t DecodeBuffer::DecodeUInt24() { + DCHECK_LE(3u, Remaining()); + const uint8_t b1 = DecodeUInt8(); + const uint8_t b2 = DecodeUInt8(); + const uint8_t b3 = DecodeUInt8(); + return b1 << 16 | b2 << 8 | b3; +} + +uint32_t DecodeBuffer::DecodeUInt31() { + DCHECK_LE(4u, Remaining()); + const uint8_t b1 = DecodeUInt8() & 0x7f; // Mask out the high order bit. + const uint8_t b2 = DecodeUInt8(); + const uint8_t b3 = DecodeUInt8(); + const uint8_t b4 = DecodeUInt8(); + return b1 << 24 | b2 << 16 | b3 << 8 | b4; +} + +uint32_t DecodeBuffer::DecodeUInt32() { + DCHECK_LE(4u, Remaining()); + const uint8_t b1 = DecodeUInt8(); + const uint8_t b2 = DecodeUInt8(); + const uint8_t b3 = DecodeUInt8(); + const uint8_t b4 = DecodeUInt8(); + return b1 << 24 | b2 << 16 | b3 << 8 | b4; +} + +#ifndef NDEBUG +void DecodeBuffer::set_subset_of_base(DecodeBuffer* base, + const DecodeBufferSubset* subset) { + DCHECK_EQ(this, subset); + base->set_subset(subset); +} +void DecodeBuffer::clear_subset_of_base(DecodeBuffer* base, + const DecodeBufferSubset* subset) { + DCHECK_EQ(this, subset); + base->clear_subset(subset); +} +void DecodeBuffer::set_subset(const DecodeBufferSubset* subset) { + DCHECK(subset != nullptr); + DCHECK_EQ(subset_, nullptr) << "There is already a subset"; + subset_ = subset; +} +void DecodeBuffer::clear_subset(const DecodeBufferSubset* subset) { + DCHECK(subset != nullptr); + DCHECK_EQ(subset_, subset); + subset_ = nullptr; +} +void DecodeBufferSubset::DebugSetup() { + start_base_offset_ = base_buffer_->Offset(); + max_base_offset_ = start_base_offset_ + FullSize(); + DCHECK_LE(max_base_offset_, base_buffer_->FullSize()); + + // Ensure that there is only one DecodeBufferSubset at a time for a base. + set_subset_of_base(base_buffer_, this); +} +void DecodeBufferSubset::DebugTearDown() { + // Ensure that the base hasn't been modified. + DCHECK_EQ(start_base_offset_, base_buffer_->Offset()) + << "The base buffer was modified"; + + // Ensure that we haven't gone beyond the maximum allowed offset. + size_t offset = Offset(); + DCHECK_LE(offset, FullSize()); + DCHECK_LE(start_base_offset_ + offset, max_base_offset_); + DCHECK_LE(max_base_offset_, base_buffer_->FullSize()); + + clear_subset_of_base(base_buffer_, this); +} +#endif + +} // namespace http2
diff --git a/http2/decoder/decode_buffer.h b/http2/decoder/decode_buffer.h new file mode 100644 index 0000000..3b213c2 --- /dev/null +++ b/http2/decoder/decode_buffer.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_DECODER_DECODE_BUFFER_H_ +#define QUICHE_HTTP2_DECODER_DECODE_BUFFER_H_ + +// DecodeBuffer provides primitives for decoding various integer types found in +// HTTP/2 frames. It wraps a byte array from which we can read and decode +// serialized HTTP/2 frames, or parts thereof. DecodeBuffer is intended only for +// stack allocation, where the caller is typically going to use the DecodeBuffer +// instance as part of decoding the entire buffer before returning to its own +// caller. + +#include <stddef.h> + +#include <algorithm> +#include <cstdint> + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { +class DecodeBufferSubset; + +class HTTP2_EXPORT_PRIVATE DecodeBuffer { + public: + DecodeBuffer(const char* buffer, size_t len) + : buffer_(buffer), cursor_(buffer), beyond_(buffer + len) { + DCHECK(buffer != nullptr); + // We assume the decode buffers will typically be modest in size (i.e. often + // a few KB, perhaps as high as 100KB). Let's make sure during testing that + // we don't go very high, with 32MB selected rather arbitrarily. + const size_t kMaxDecodeBufferLength = 1 << 25; + DCHECK_LE(len, kMaxDecodeBufferLength); + } + explicit DecodeBuffer(Http2StringPiece s) + : DecodeBuffer(s.data(), s.size()) {} + // Constructor for character arrays, typically in tests. For example: + // const char input[] = { 0x11 }; + // DecodeBuffer b(input); + template <size_t N> + explicit DecodeBuffer(const char (&buf)[N]) : DecodeBuffer(buf, N) {} + + DecodeBuffer(const DecodeBuffer&) = delete; + DecodeBuffer operator=(const DecodeBuffer&) = delete; + + bool Empty() const { return cursor_ >= beyond_; } + bool HasData() const { return cursor_ < beyond_; } + size_t Remaining() const { + DCHECK_LE(cursor_, beyond_); + return beyond_ - cursor_; + } + size_t Offset() const { return cursor_ - buffer_; } + size_t FullSize() const { return beyond_ - buffer_; } + + // Returns the minimum of the number of bytes remaining in this DecodeBuffer + // and |length|, in support of determining how much of some structure/payload + // is in this DecodeBuffer. + size_t MinLengthRemaining(size_t length) const { + return std::min(length, Remaining()); + } + + // For string decoding, returns a pointer to the next byte/char to be decoded. + const char* cursor() const { return cursor_; } + // Advances the cursor (pointer to the next byte/char to be decoded). + void AdvanceCursor(size_t amount) { + DCHECK_LE(amount, Remaining()); // Need at least that much remaining. + DCHECK_EQ(subset_, nullptr) << "Access via subset only when present."; + cursor_ += amount; + } + + // Only call methods starting "Decode" when there is enough input remaining. + char DecodeChar() { + DCHECK_LE(1u, Remaining()); // Need at least one byte remaining. + DCHECK_EQ(subset_, nullptr) << "Access via subset only when present."; + return *cursor_++; + } + + uint8_t DecodeUInt8(); + uint16_t DecodeUInt16(); + uint32_t DecodeUInt24(); + + // For 31-bit unsigned integers, where the 32nd bit is reserved for future + // use (i.e. the high-bit of the first byte of the encoding); examples: + // the Stream Id in a frame header or the Window Size Increment in a + // WINDOW_UPDATE frame. + uint32_t DecodeUInt31(); + + uint32_t DecodeUInt32(); + + protected: +#ifndef NDEBUG + // These are part of validating during tests that there is at most one + // DecodeBufferSubset instance at a time for any DecodeBuffer instance. + void set_subset_of_base(DecodeBuffer* base, const DecodeBufferSubset* subset); + void clear_subset_of_base(DecodeBuffer* base, + const DecodeBufferSubset* subset); +#endif + + private: +#ifndef NDEBUG + void set_subset(const DecodeBufferSubset* subset); + void clear_subset(const DecodeBufferSubset* subset); +#endif + + // Prevent heap allocation of DecodeBuffer. + static void* operator new(size_t s); + static void* operator new[](size_t s); + static void operator delete(void* p); + static void operator delete[](void* p); + + const char* const buffer_; + const char* cursor_; + const char* const beyond_; + const DecodeBufferSubset* subset_ = nullptr; // Used for DCHECKs. +}; + +// DecodeBufferSubset is used when decoding a known sized chunk of data, which +// starts at base->cursor(), and continues for subset_len, which may be +// entirely in |base|, or may extend beyond it (hence the MinLengthRemaining +// in the constructor). +// There are two benefits to using DecodeBufferSubset: it ensures that the +// cursor of |base| is advanced when the subset's destructor runs, and it +// ensures that the consumer of the subset can't go beyond the subset which +// it is intended to decode. +// There must be only a single DecodeBufferSubset at a time for a base +// DecodeBuffer, though they can be nested (i.e. a DecodeBufferSubset's +// base may itself be a DecodeBufferSubset). This avoids the AdvanceCursor +// being called erroneously. +class HTTP2_EXPORT_PRIVATE DecodeBufferSubset : public DecodeBuffer { + public: + DecodeBufferSubset(DecodeBuffer* base, size_t subset_len) + : DecodeBuffer(base->cursor(), base->MinLengthRemaining(subset_len)), + base_buffer_(base) { +#ifndef NDEBUG + DebugSetup(); +#endif + } + + DecodeBufferSubset(const DecodeBufferSubset&) = delete; + DecodeBufferSubset operator=(const DecodeBufferSubset&) = delete; + + ~DecodeBufferSubset() { + size_t offset = Offset(); +#ifndef NDEBUG + DebugTearDown(); +#endif + base_buffer_->AdvanceCursor(offset); + } + + private: + DecodeBuffer* const base_buffer_; +#ifndef NDEBUG + size_t start_base_offset_; // Used for DCHECKs. + size_t max_base_offset_; // Used for DCHECKs. + + void DebugSetup(); + void DebugTearDown(); +#endif +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_DECODER_DECODE_BUFFER_H_
diff --git a/http2/decoder/decode_buffer_test.cc b/http2/decoder/decode_buffer_test.cc new file mode 100644 index 0000000..e8e3d3b --- /dev/null +++ b/http2/decoder/decode_buffer_test.cc
@@ -0,0 +1,203 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" + +#include <functional> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" + +namespace http2 { +namespace test { +namespace { + +enum class TestEnumClass32 { + kValue1 = 1, + kValue99 = 99, + kValue1M = 1000000, +}; + +enum class TestEnumClass8 { + kValue1 = 1, + kValue2 = 1, + kValue99 = 99, + kValue255 = 255, +}; + +enum TestEnum8 { + kMaskLo = 0x01, + kMaskHi = 0x80, +}; + +struct TestStruct { + uint8_t f1; + uint16_t f2; + uint32_t f3; // Decoded as a uint24 + uint32_t f4; + uint32_t f5; // Decoded as if uint31 + TestEnumClass32 f6; + TestEnumClass8 f7; + TestEnum8 f8; +}; + +class DecodeBufferTest : public ::testing::Test { + protected: + Http2Random random_; + uint32_t decode_offset_; +}; + +TEST_F(DecodeBufferTest, DecodesFixedInts) { + const char data[] = "\x01\x12\x23\x34\x45\x56\x67\x78\x89\x9a"; + DecodeBuffer b1(data, strlen(data)); + EXPECT_EQ(1, b1.DecodeUInt8()); + EXPECT_EQ(0x1223u, b1.DecodeUInt16()); + EXPECT_EQ(0x344556u, b1.DecodeUInt24()); + EXPECT_EQ(0x6778899Au, b1.DecodeUInt32()); +} + +// Make sure that DecodeBuffer is not copying input, just pointing into +// provided input buffer. +TEST_F(DecodeBufferTest, HasNotCopiedInput) { + const char data[] = "ab"; + DecodeBuffer b1(data, 2); + + EXPECT_EQ(2u, b1.Remaining()); + EXPECT_EQ(0u, b1.Offset()); + EXPECT_FALSE(b1.Empty()); + EXPECT_EQ(data, b1.cursor()); // cursor points to input buffer + EXPECT_TRUE(b1.HasData()); + + b1.AdvanceCursor(1); + + EXPECT_EQ(1u, b1.Remaining()); + EXPECT_EQ(1u, b1.Offset()); + EXPECT_FALSE(b1.Empty()); + EXPECT_EQ(&data[1], b1.cursor()); + EXPECT_TRUE(b1.HasData()); + + b1.AdvanceCursor(1); + + EXPECT_EQ(0u, b1.Remaining()); + EXPECT_EQ(2u, b1.Offset()); + EXPECT_TRUE(b1.Empty()); + EXPECT_EQ(&data[2], b1.cursor()); + EXPECT_FALSE(b1.HasData()); + + DecodeBuffer b2(data, 0); + + EXPECT_EQ(0u, b2.Remaining()); + EXPECT_EQ(0u, b2.Offset()); + EXPECT_TRUE(b2.Empty()); + EXPECT_EQ(data, b2.cursor()); + EXPECT_FALSE(b2.HasData()); +} + +// DecodeBufferSubset can't go beyond the end of the base buffer. +TEST_F(DecodeBufferTest, DecodeBufferSubsetLimited) { + const char data[] = "abc"; + DecodeBuffer base(data, 3); + base.AdvanceCursor(1); + DecodeBufferSubset subset(&base, 100); + EXPECT_EQ(2u, subset.FullSize()); +} + +// DecodeBufferSubset advances the cursor of its base upon destruction. +TEST_F(DecodeBufferTest, DecodeBufferSubsetAdvancesCursor) { + const char data[] = "abc"; + const size_t size = sizeof(data) - 1; + EXPECT_EQ(3u, size); + DecodeBuffer base(data, size); + { + // First no change to the cursor. + DecodeBufferSubset subset(&base, size + 100); + EXPECT_EQ(size, subset.FullSize()); + EXPECT_EQ(base.FullSize(), subset.FullSize()); + EXPECT_EQ(0u, subset.Offset()); + } + EXPECT_EQ(0u, base.Offset()); + EXPECT_EQ(size, base.Remaining()); +} + +// Make sure that DecodeBuffer ctor complains about bad args. +#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG) +TEST(DecodeBufferDeathTest, NonNullBufferRequired) { + EXPECT_DEBUG_DEATH({ DecodeBuffer b(nullptr, 3); }, "nullptr"); +} + +// Make sure that DecodeBuffer ctor complains about bad args. +TEST(DecodeBufferDeathTest, ModestBufferSizeRequired) { + EXPECT_DEBUG_DEATH( + { + const char data[] = "abc"; + size_t len = 0; + DecodeBuffer b(data, ~len); + }, + "Max.*Length"); +} + +// Make sure that DecodeBuffer detects advance beyond end, in debug mode. +TEST(DecodeBufferDeathTest, LimitedAdvance) { + { + // Advance right up to end is OK. + const char data[] = "abc"; + DecodeBuffer b(data, 3); + b.AdvanceCursor(3); // OK + EXPECT_TRUE(b.Empty()); + } + EXPECT_DEBUG_DEATH( + { + // Going beyond is not OK. + const char data[] = "abc"; + DecodeBuffer b(data, 3); + b.AdvanceCursor(4); + }, + "4 vs. 3"); +} + +// Make sure that DecodeBuffer detects decode beyond end, in debug mode. +TEST(DecodeBufferDeathTest, DecodeUInt8PastEnd) { + const char data[] = {0x12, 0x23}; + DecodeBuffer b(data, sizeof data); + EXPECT_EQ(2u, b.FullSize()); + EXPECT_EQ(0x1223, b.DecodeUInt16()); + EXPECT_DEBUG_DEATH({ b.DecodeUInt8(); }, "1 vs. 0"); +} + +// Make sure that DecodeBuffer detects decode beyond end, in debug mode. +TEST(DecodeBufferDeathTest, DecodeUInt16OverEnd) { + const char data[] = {0x12, 0x23, 0x34}; + DecodeBuffer b(data, sizeof data); + EXPECT_EQ(3u, b.FullSize()); + EXPECT_EQ(0x1223, b.DecodeUInt16()); + EXPECT_DEBUG_DEATH({ b.DecodeUInt16(); }, "2 vs. 1"); +} + +// Make sure that DecodeBuffer doesn't agree with having two subsets. +TEST(DecodeBufferSubsetDeathTest, TwoSubsets) { + const char data[] = "abc"; + DecodeBuffer base(data, 3); + DecodeBufferSubset subset1(&base, 1); + EXPECT_DEBUG_DEATH({ DecodeBufferSubset subset2(&base, 1); }, + "There is already a subset"); +} + +// Make sure that DecodeBufferSubset notices when the base's cursor has moved. +TEST(DecodeBufferSubsetDeathTest, BaseCursorAdvanced) { + const char data[] = "abc"; + DecodeBuffer base(data, 3); + base.AdvanceCursor(1); + EXPECT_DEBUG_DEATH( + { + DecodeBufferSubset subset1(&base, 2); + base.AdvanceCursor(1); + }, + "Access via subset only when present"); +} +#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG) + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/decoder/decode_http2_structures.cc b/http2/decoder/decode_http2_structures.cc new file mode 100644 index 0000000..71f6cce --- /dev/null +++ b/http2/decoder/decode_http2_structures.cc
@@ -0,0 +1,111 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/decode_http2_structures.h" + +#include <cstdint> + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" + +namespace http2 { + +// Http2FrameHeader decoding: + +void DoDecode(Http2FrameHeader* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_LE(Http2FrameHeader::EncodedSize(), b->Remaining()); + out->payload_length = b->DecodeUInt24(); + out->type = static_cast<Http2FrameType>(b->DecodeUInt8()); + out->flags = static_cast<Http2FrameFlag>(b->DecodeUInt8()); + out->stream_id = b->DecodeUInt31(); +} + +// Http2PriorityFields decoding: + +void DoDecode(Http2PriorityFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_LE(Http2PriorityFields::EncodedSize(), b->Remaining()); + uint32_t stream_id_and_flag = b->DecodeUInt32(); + out->stream_dependency = stream_id_and_flag & StreamIdMask(); + if (out->stream_dependency == stream_id_and_flag) { + out->is_exclusive = false; + } else { + out->is_exclusive = true; + } + // Note that chars are automatically promoted to ints during arithmetic, + // so 255 + 1 doesn't end up as zero. + out->weight = b->DecodeUInt8() + 1; +} + +// Http2RstStreamFields decoding: + +void DoDecode(Http2RstStreamFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_LE(Http2RstStreamFields::EncodedSize(), b->Remaining()); + out->error_code = static_cast<Http2ErrorCode>(b->DecodeUInt32()); +} + +// Http2SettingFields decoding: + +void DoDecode(Http2SettingFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_LE(Http2SettingFields::EncodedSize(), b->Remaining()); + out->parameter = static_cast<Http2SettingsParameter>(b->DecodeUInt16()); + out->value = b->DecodeUInt32(); +} + +// Http2PushPromiseFields decoding: + +void DoDecode(Http2PushPromiseFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_LE(Http2PushPromiseFields::EncodedSize(), b->Remaining()); + out->promised_stream_id = b->DecodeUInt31(); +} + +// Http2PingFields decoding: + +void DoDecode(Http2PingFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_LE(Http2PingFields::EncodedSize(), b->Remaining()); + memcpy(out->opaque_bytes, b->cursor(), Http2PingFields::EncodedSize()); + b->AdvanceCursor(Http2PingFields::EncodedSize()); +} + +// Http2GoAwayFields decoding: + +void DoDecode(Http2GoAwayFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_LE(Http2GoAwayFields::EncodedSize(), b->Remaining()); + out->last_stream_id = b->DecodeUInt31(); + out->error_code = static_cast<Http2ErrorCode>(b->DecodeUInt32()); +} + +// Http2WindowUpdateFields decoding: + +void DoDecode(Http2WindowUpdateFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_LE(Http2WindowUpdateFields::EncodedSize(), b->Remaining()); + out->window_size_increment = b->DecodeUInt31(); +} + +// Http2AltSvcFields decoding: + +void DoDecode(Http2AltSvcFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_LE(Http2AltSvcFields::EncodedSize(), b->Remaining()); + out->origin_length = b->DecodeUInt16(); +} + +} // namespace http2
diff --git a/http2/decoder/decode_http2_structures.h b/http2/decoder/decode_http2_structures.h new file mode 100644 index 0000000..3f6a317 --- /dev/null +++ b/http2/decoder/decode_http2_structures.h
@@ -0,0 +1,34 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_DECODER_DECODE_HTTP2_STRUCTURES_H_ +#define QUICHE_HTTP2_DECODER_DECODE_HTTP2_STRUCTURES_H_ + +// Provides functions for decoding the fixed size structures in the HTTP/2 spec. + +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" + +namespace http2 { + +// DoDecode(STRUCTURE* out, DecodeBuffer* b) decodes the structure from start +// to end, advancing the cursor by STRUCTURE::EncodedSize(). The decode buffer +// must be large enough (i.e. b->Remaining() >= STRUCTURE::EncodedSize()). + +HTTP2_EXPORT_PRIVATE void DoDecode(Http2FrameHeader* out, DecodeBuffer* b); +HTTP2_EXPORT_PRIVATE void DoDecode(Http2PriorityFields* out, DecodeBuffer* b); +HTTP2_EXPORT_PRIVATE void DoDecode(Http2RstStreamFields* out, DecodeBuffer* b); +HTTP2_EXPORT_PRIVATE void DoDecode(Http2SettingFields* out, DecodeBuffer* b); +HTTP2_EXPORT_PRIVATE void DoDecode(Http2PushPromiseFields* out, + DecodeBuffer* b); +HTTP2_EXPORT_PRIVATE void DoDecode(Http2PingFields* out, DecodeBuffer* b); +HTTP2_EXPORT_PRIVATE void DoDecode(Http2GoAwayFields* out, DecodeBuffer* b); +HTTP2_EXPORT_PRIVATE void DoDecode(Http2WindowUpdateFields* out, + DecodeBuffer* b); +HTTP2_EXPORT_PRIVATE void DoDecode(Http2AltSvcFields* out, DecodeBuffer* b); + +} // namespace http2 + +#endif // QUICHE_HTTP2_DECODER_DECODE_HTTP2_STRUCTURES_H_
diff --git a/http2/decoder/decode_http2_structures_test.cc b/http2/decoder/decode_http2_structures_test.cc new file mode 100644 index 0000000..43bd7e8 --- /dev/null +++ b/http2/decoder/decode_http2_structures_test.cc
@@ -0,0 +1,461 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/decode_http2_structures.h" + +// Tests decoding all of the fixed size HTTP/2 structures (i.e. those defined +// in net/third_party/quiche/src/http2/http2_structures.h). + +#include <stddef.h> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures_test_util.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h" + +using ::testing::AssertionResult; + +namespace http2 { +namespace test { +namespace { + +template <typename T, size_t N> +Http2StringPiece ToStringPiece(T (&data)[N]) { + return Http2StringPiece(reinterpret_cast<const char*>(data), N * sizeof(T)); +} + +template <class S> +Http2String SerializeStructure(const S& s) { + Http2FrameBuilder fb; + fb.Append(s); + EXPECT_EQ(S::EncodedSize(), fb.size()); + return fb.buffer(); +} + +template <class S> +class StructureDecoderTest : public ::testing::Test { + protected: + typedef S Structure; + + StructureDecoderTest() : random_(), random_decode_count_(100) {} + + // Set the fields of |*p| to random values. + void Randomize(S* p) { ::http2::test::Randomize(p, &random_); } + + // Fully decodes the Structure at the start of data, and confirms it matches + // *expected (if provided). + void DecodeLeadingStructure(const S* expected, Http2StringPiece data) { + ASSERT_LE(S::EncodedSize(), data.size()); + DecodeBuffer db(data); + Randomize(&structure_); + DoDecode(&structure_, &db); + EXPECT_EQ(db.Offset(), S::EncodedSize()); + if (expected != nullptr) { + EXPECT_EQ(structure_, *expected); + } + } + + template <size_t N> + void DecodeLeadingStructure(const char (&data)[N]) { + DecodeLeadingStructure(nullptr, Http2StringPiece(data, N)); + } + + // Encode the structure |in_s| into bytes, then decode the bytes + // and validate that the decoder produced the same field values. + void EncodeThenDecode(const S& in_s) { + Http2String bytes = SerializeStructure(in_s); + EXPECT_EQ(S::EncodedSize(), bytes.size()); + DecodeLeadingStructure(&in_s, bytes); + } + + // Generate + void TestDecodingRandomizedStructures(size_t count) { + for (size_t i = 0; i < count && !HasFailure(); ++i) { + Structure input; + Randomize(&input); + EncodeThenDecode(input); + } + } + + void TestDecodingRandomizedStructures() { + TestDecodingRandomizedStructures(random_decode_count_); + } + + Http2Random random_; + const size_t random_decode_count_; + uint32_t decode_offset_ = 0; + S structure_; + size_t fast_decode_count_ = 0; + size_t slow_decode_count_ = 0; +}; + +class FrameHeaderDecoderTest : public StructureDecoderTest<Http2FrameHeader> {}; + +TEST_F(FrameHeaderDecoderTest, DecodesLiteral) { + { + // Realistic input. + const char kData[] = { + '\x00', '\x00', '\x05', // Payload length: 5 + '\x01', // Frame type: HEADERS + '\x08', // Flags: PADDED + '\x00', '\x00', '\x00', '\x01', // Stream ID: 1 + '\x04', // Padding length: 4 + '\x00', '\x00', '\x00', '\x00', // Padding bytes + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(5u, structure_.payload_length); + EXPECT_EQ(Http2FrameType::HEADERS, structure_.type); + EXPECT_EQ(Http2FrameFlag::PADDED, structure_.flags); + EXPECT_EQ(1u, structure_.stream_id); + } + } + { + // Unlikely input. + const char kData[] = { + '\xff', '\xff', '\xff', // Payload length: uint24 max + '\xff', // Frame type: Unknown + '\xff', // Flags: Unknown/All + '\xff', '\xff', '\xff', '\xff', // Stream ID: uint31 max, plus R-bit + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ((1u << 24) - 1, structure_.payload_length); + EXPECT_EQ(static_cast<Http2FrameType>(255), structure_.type); + EXPECT_EQ(255, structure_.flags); + EXPECT_EQ(0x7FFFFFFFu, structure_.stream_id); + } + } +} + +TEST_F(FrameHeaderDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class PriorityFieldsDecoderTest + : public StructureDecoderTest<Http2PriorityFields> {}; + +TEST_F(PriorityFieldsDecoderTest, DecodesLiteral) { + { + const char kData[] = { + '\x80', '\x00', '\x00', '\x05', // Exclusive (yes) and Dependency (5) + '\xff', // Weight: 256 (after adding 1) + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(5u, structure_.stream_dependency); + EXPECT_EQ(256u, structure_.weight); + EXPECT_EQ(true, structure_.is_exclusive); + } + } + { + const char kData[] = { + '\x7f', '\xff', + '\xff', '\xff', // Exclusive (no) and Dependency (0x7fffffff) + '\x00', // Weight: 1 (after adding 1) + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(StreamIdMask(), structure_.stream_dependency); + EXPECT_EQ(1u, structure_.weight); + EXPECT_FALSE(structure_.is_exclusive); + } + } +} + +TEST_F(PriorityFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class RstStreamFieldsDecoderTest + : public StructureDecoderTest<Http2RstStreamFields> {}; + +TEST_F(RstStreamFieldsDecoderTest, DecodesLiteral) { + { + const char kData[] = { + '\x00', '\x00', '\x00', '\x01', // Error: PROTOCOL_ERROR + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_TRUE(structure_.IsSupportedErrorCode()); + EXPECT_EQ(Http2ErrorCode::PROTOCOL_ERROR, structure_.error_code); + } + } + { + const char kData[] = { + '\xff', '\xff', '\xff', + '\xff', // Error: max uint32 (Unknown error code) + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_FALSE(structure_.IsSupportedErrorCode()); + EXPECT_EQ(static_cast<Http2ErrorCode>(0xffffffff), structure_.error_code); + } + } +} + +TEST_F(RstStreamFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class SettingFieldsDecoderTest + : public StructureDecoderTest<Http2SettingFields> {}; + +TEST_F(SettingFieldsDecoderTest, DecodesLiteral) { + { + const char kData[] = { + '\x00', '\x01', // Setting: HEADER_TABLE_SIZE + '\x00', '\x00', '\x40', '\x00', // Value: 16K + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_TRUE(structure_.IsSupportedParameter()); + EXPECT_EQ(Http2SettingsParameter::HEADER_TABLE_SIZE, + structure_.parameter); + EXPECT_EQ(1u << 14, structure_.value); + } + } + { + const char kData[] = { + '\x00', '\x00', // Setting: Unknown (0) + '\xff', '\xff', '\xff', '\xff', // Value: max uint32 + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_FALSE(structure_.IsSupportedParameter()); + EXPECT_EQ(static_cast<Http2SettingsParameter>(0), structure_.parameter); + } + } +} + +TEST_F(SettingFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class PushPromiseFieldsDecoderTest + : public StructureDecoderTest<Http2PushPromiseFields> {}; + +TEST_F(PushPromiseFieldsDecoderTest, DecodesLiteral) { + { + const char kData[] = { + '\x00', '\x01', '\x8a', '\x92', // Promised Stream ID: 101010 + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(101010u, structure_.promised_stream_id); + } + } + { + // Promised stream id has R-bit (reserved for future use) set, which + // should be cleared by the decoder. + const char kData[] = { + '\xff', '\xff', '\xff', + '\xff', // Promised Stream ID: max uint31 and R-bit + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(StreamIdMask(), structure_.promised_stream_id); + } + } +} + +TEST_F(PushPromiseFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class PingFieldsDecoderTest : public StructureDecoderTest<Http2PingFields> {}; + +TEST_F(PingFieldsDecoderTest, DecodesLiteral) { + { + // Each byte is different, so can detect if order changed. + const char kData[] = { + '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(Http2StringPiece(kData, 8), + ToStringPiece(structure_.opaque_bytes)); + } + } + { + // All zeros, detect problems handling NULs. + const char kData[] = { + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(Http2StringPiece(kData, 8), + ToStringPiece(structure_.opaque_bytes)); + } + } + { + const char kData[] = { + '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(Http2StringPiece(kData, 8), + ToStringPiece(structure_.opaque_bytes)); + } + } +} + +TEST_F(PingFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class GoAwayFieldsDecoderTest : public StructureDecoderTest<Http2GoAwayFields> { +}; + +TEST_F(GoAwayFieldsDecoderTest, DecodesLiteral) { + { + const char kData[] = { + '\x00', '\x00', '\x00', '\x00', // Last Stream ID: 0 + '\x00', '\x00', '\x00', '\x00', // Error: NO_ERROR (0) + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(0u, structure_.last_stream_id); + EXPECT_TRUE(structure_.IsSupportedErrorCode()); + EXPECT_EQ(Http2ErrorCode::HTTP2_NO_ERROR, structure_.error_code); + } + } + { + const char kData[] = { + '\x00', '\x00', '\x00', '\x01', // Last Stream ID: 1 + '\x00', '\x00', '\x00', '\x0d', // Error: HTTP_1_1_REQUIRED + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(1u, structure_.last_stream_id); + EXPECT_TRUE(structure_.IsSupportedErrorCode()); + EXPECT_EQ(Http2ErrorCode::HTTP_1_1_REQUIRED, structure_.error_code); + } + } + { + const char kData[] = { + '\xff', '\xff', + '\xff', '\xff', // Last Stream ID: max uint31 and R-bit + '\xff', '\xff', + '\xff', '\xff', // Error: max uint32 (Unknown error code) + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(StreamIdMask(), structure_.last_stream_id); // No high-bit. + EXPECT_FALSE(structure_.IsSupportedErrorCode()); + EXPECT_EQ(static_cast<Http2ErrorCode>(0xffffffff), structure_.error_code); + } + } +} + +TEST_F(GoAwayFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class WindowUpdateFieldsDecoderTest + : public StructureDecoderTest<Http2WindowUpdateFields> {}; + +TEST_F(WindowUpdateFieldsDecoderTest, DecodesLiteral) { + { + const char kData[] = { + '\x00', '\x01', '\x00', '\x00', // Window Size Increment: 2 ^ 16 + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(1u << 16, structure_.window_size_increment); + } + } + { + // Increment must be non-zero, but we need to be able to decode the invalid + // zero to detect it. + const char kData[] = { + '\x00', '\x00', '\x00', '\x00', // Window Size Increment: 0 + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(0u, structure_.window_size_increment); + } + } + { + // Increment has R-bit (reserved for future use) set, which + // should be cleared by the decoder. + // clang-format off + const char kData[] = { + // Window Size Increment: max uint31 and R-bit + '\xff', '\xff', '\xff', '\xff', + }; + // clang-format on + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(StreamIdMask(), structure_.window_size_increment); + } + } +} + +TEST_F(WindowUpdateFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class AltSvcFieldsDecoderTest : public StructureDecoderTest<Http2AltSvcFields> { +}; + +TEST_F(AltSvcFieldsDecoderTest, DecodesLiteral) { + { + const char kData[] = { + '\x00', '\x00', // Origin Length: 0 + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(0, structure_.origin_length); + } + } + { + const char kData[] = { + '\x00', '\x14', // Origin Length: 20 + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(20, structure_.origin_length); + } + } + { + const char kData[] = { + '\xff', '\xff', // Origin Length: uint16 max + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(65535, structure_.origin_length); + } + } +} + +TEST_F(AltSvcFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/decoder/decode_status.cc b/http2/decoder/decode_status.cc new file mode 100644 index 0000000..6a913f0 --- /dev/null +++ b/http2/decoder/decode_status.cc
@@ -0,0 +1,28 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h" + +namespace http2 { + +std::ostream& operator<<(std::ostream& out, DecodeStatus v) { + switch (v) { + case DecodeStatus::kDecodeDone: + return out << "DecodeDone"; + case DecodeStatus::kDecodeInProgress: + return out << "DecodeInProgress"; + case DecodeStatus::kDecodeError: + return out << "DecodeError"; + } + // 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 << "Unknown DecodeStatus " << unknown; + return out << "DecodeStatus(" << unknown << ")"; +} + +} // namespace http2
diff --git a/http2/decoder/decode_status.h b/http2/decoder/decode_status.h new file mode 100644 index 0000000..fdecd22 --- /dev/null +++ b/http2/decoder/decode_status.h
@@ -0,0 +1,33 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_DECODER_DECODE_STATUS_H_ +#define QUICHE_HTTP2_DECODER_DECODE_STATUS_H_ + +// Enum DecodeStatus is used to report the status of decoding of many +// types of HTTP/2 and HPACK objects. + +#include <ostream> + +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" + +namespace http2 { + +enum class DecodeStatus { + // Decoding is done. + kDecodeDone, + + // Decoder needs more input to be able to make progress. + kDecodeInProgress, + + // Decoding failed (e.g. HPACK variable length integer is too large, or + // an HTTP/2 frame has padding declared to be larger than the payload). + kDecodeError, +}; +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + DecodeStatus v); + +} // namespace http2 + +#endif // QUICHE_HTTP2_DECODER_DECODE_STATUS_H_
diff --git a/http2/decoder/frame_decoder_state.cc b/http2/decoder/frame_decoder_state.cc new file mode 100644 index 0000000..946a6ee --- /dev/null +++ b/http2/decoder/frame_decoder_state.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 "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h" + +namespace http2 { + +DecodeStatus FrameDecoderState::ReadPadLength(DecodeBuffer* db, + bool report_pad_length) { + DVLOG(2) << "ReadPadLength db->Remaining=" << db->Remaining() + << "; payload_length=" << frame_header().payload_length; + DCHECK(IsPaddable()); + DCHECK(frame_header().IsPadded()); + + // Pad Length is always at the start of the frame, so remaining_payload_ + // should equal payload_length at this point. + const uint32_t total_payload = frame_header().payload_length; + DCHECK_EQ(total_payload, remaining_payload_); + DCHECK_EQ(0u, remaining_padding_); + + if (db->HasData()) { + const uint32_t pad_length = db->DecodeUInt8(); + const uint32_t total_padding = pad_length + 1; + if (total_padding <= total_payload) { + remaining_padding_ = pad_length; + remaining_payload_ = total_payload - total_padding; + if (report_pad_length) { + listener()->OnPadLength(pad_length); + } + return DecodeStatus::kDecodeDone; + } + const uint32_t missing_length = total_padding - total_payload; + // To allow for the possibility of recovery, record the number of + // remaining bytes of the frame's payload (invalid though it is) + // in remaining_payload_. + remaining_payload_ = total_payload - 1; // 1 for sizeof(Pad Length). + remaining_padding_ = 0; + listener()->OnPaddingTooLong(frame_header(), missing_length); + return DecodeStatus::kDecodeError; + } + + if (total_payload == 0) { + remaining_payload_ = 0; + remaining_padding_ = 0; + listener()->OnPaddingTooLong(frame_header(), 1); + return DecodeStatus::kDecodeError; + } + // Need to wait for another buffer. + return DecodeStatus::kDecodeInProgress; +} + +bool FrameDecoderState::SkipPadding(DecodeBuffer* db) { + DVLOG(2) << "SkipPadding remaining_padding_=" << remaining_padding_ + << ", db->Remaining=" << db->Remaining() + << ", header: " << frame_header(); + DCHECK_EQ(remaining_payload_, 0u); + DCHECK(IsPaddable()) << "header: " << frame_header(); + DCHECK_GE(remaining_padding_, 0u); + DCHECK(remaining_padding_ == 0 || frame_header().IsPadded()) + << "remaining_padding_=" << remaining_padding_ + << ", header: " << frame_header(); + const size_t avail = AvailablePadding(db); + if (avail > 0) { + listener()->OnPadding(db->cursor(), avail); + db->AdvanceCursor(avail); + remaining_padding_ -= avail; + } + return remaining_padding_ == 0; +} + +DecodeStatus FrameDecoderState::ReportFrameSizeError() { + DVLOG(2) << "FrameDecoderState::ReportFrameSizeError: " + << " remaining_payload_=" << remaining_payload_ + << "; remaining_padding_=" << remaining_padding_ + << ", header: " << frame_header(); + listener()->OnFrameSizeError(frame_header()); + return DecodeStatus::kDecodeError; +} + +} // namespace http2
diff --git a/http2/decoder/frame_decoder_state.h b/http2/decoder/frame_decoder_state.h new file mode 100644 index 0000000..1581752 --- /dev/null +++ b/http2/decoder/frame_decoder_state.h
@@ -0,0 +1,250 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_DECODER_FRAME_DECODER_STATE_H_ +#define QUICHE_HTTP2_DECODER_FRAME_DECODER_STATE_H_ + +// FrameDecoderState provides state and behaviors in support of decoding +// the common frame header and the payload of all frame types. +// It is an input to all of the payload decoders. + +// TODO(jamessynge): Since FrameDecoderState has far more than state in it, +// rename to FrameDecoderHelper, or similar. + +#include <stddef.h> + +#include <cstdint> + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/decoder/http2_structure_decoder.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" + +namespace http2 { +namespace test { +class FrameDecoderStatePeer; +} // namespace test + +class HTTP2_EXPORT_PRIVATE FrameDecoderState { + public: + FrameDecoderState() {} + + // Sets the listener which the decoders should call as they decode HTTP/2 + // frames. The listener can be changed at any time, which allows for replacing + // it with a no-op listener when an error is detected, either by the payload + // decoder (OnPaddingTooLong or OnFrameSizeError) or by the "real" listener. + // That in turn allows us to define Http2FrameDecoderListener such that all + // methods have return type void, with no direct way to indicate whether the + // decoder should stop, and to eliminate from the decoder all checks of the + // return value. Instead the listener/caller can simply replace the current + // listener with a no-op listener implementation. + // TODO(jamessynge): Make set_listener private as only Http2FrameDecoder + // and tests need to set it, so it doesn't need to be public. + void set_listener(Http2FrameDecoderListener* listener) { + listener_ = listener; + } + Http2FrameDecoderListener* listener() const { return listener_; } + + // The most recently decoded frame header. + const Http2FrameHeader& frame_header() const { return frame_header_; } + + // Decode a structure in the payload, adjusting remaining_payload_ to account + // for the consumed portion of the payload. Returns kDecodeDone when fully + // decoded, kDecodeError if it ran out of payload before decoding completed, + // and kDecodeInProgress if the decode buffer didn't have enough of the + // remaining payload. + template <class S> + DecodeStatus StartDecodingStructureInPayload(S* out, DecodeBuffer* db) { + DVLOG(2) << __func__ << "\n\tdb->Remaining=" << db->Remaining() + << "\n\tremaining_payload_=" << remaining_payload_ + << "\n\tneed=" << S::EncodedSize(); + DecodeStatus status = + structure_decoder_.Start(out, db, &remaining_payload_); + if (status != DecodeStatus::kDecodeError) { + return status; + } + DVLOG(2) << "StartDecodingStructureInPayload: detected frame size error"; + return ReportFrameSizeError(); + } + + // Resume decoding of a structure that has been split across buffers, + // adjusting remaining_payload_ to account for the consumed portion of + // the payload. Returns values are as for StartDecodingStructureInPayload. + template <class S> + DecodeStatus ResumeDecodingStructureInPayload(S* out, DecodeBuffer* db) { + DVLOG(2) << __func__ << "\n\tdb->Remaining=" << db->Remaining() + << "\n\tremaining_payload_=" << remaining_payload_; + if (structure_decoder_.Resume(out, db, &remaining_payload_)) { + return DecodeStatus::kDecodeDone; + } else if (remaining_payload_ > 0) { + return DecodeStatus::kDecodeInProgress; + } else { + DVLOG(2) << "ResumeDecodingStructureInPayload: detected frame size error"; + return ReportFrameSizeError(); + } + } + + // Initializes the two remaining* fields, which is needed if the frame's + // payload is split across buffers, or the decoder calls ReadPadLength or + // StartDecodingStructureInPayload, and of course the methods below which + // read those fields, as their names imply. + void InitializeRemainders() { + remaining_payload_ = frame_header().payload_length; + // Note that remaining_total_payload() relies on remaining_padding_ being + // zero for frames that have no padding. + remaining_padding_ = 0; + } + + // Returns the number of bytes of the frame's payload that remain to be + // decoded, including any trailing padding. This method must only be called + // after the variables have been initialized, which in practice means once a + // payload decoder has called InitializeRemainders and/or ReadPadLength. + size_t remaining_total_payload() const { + DCHECK(IsPaddable() || remaining_padding_ == 0) << frame_header(); + return remaining_payload_ + remaining_padding_; + } + + // Returns the number of bytes of the frame's payload that remain to be + // decoded, excluding any trailing padding. This method must only be called + // after the variable has been initialized, which in practice means once a + // payload decoder has called InitializeRemainders; ReadPadLength will deduct + // the total number of padding bytes from remaining_payload_, including the + // size of the Pad Length field itself (1 byte). + size_t remaining_payload() const { return remaining_payload_; } + + // Returns the number of bytes of the frame's payload that remain to be + // decoded, including any trailing padding. This method must only be called if + // the frame type allows padding, and after the variable has been initialized, + // which in practice means once a payload decoder has called + // InitializeRemainders and/or ReadPadLength. + size_t remaining_payload_and_padding() const { + DCHECK(IsPaddable()) << frame_header(); + return remaining_payload_ + remaining_padding_; + } + + // Returns the number of bytes of trailing padding after the payload that + // remain to be decoded. This method must only be called if the frame type + // allows padding, and after the variable has been initialized, which in + // practice means once a payload decoder has called InitializeRemainders, + // and isn't set to a non-zero value until ReadPadLength has been called. + uint32_t remaining_padding() const { + DCHECK(IsPaddable()) << frame_header(); + return remaining_padding_; + } + + // How many bytes of the remaining payload are in db? + size_t AvailablePayload(DecodeBuffer* db) const { + return db->MinLengthRemaining(remaining_payload_); + } + + // How many bytes of the remaining payload and padding are in db? + // Call only for frames whose type is paddable. + size_t AvailablePayloadAndPadding(DecodeBuffer* db) const { + DCHECK(IsPaddable()) << frame_header(); + return db->MinLengthRemaining(remaining_payload_ + remaining_padding_); + } + + // How many bytes of the padding that have not yet been skipped are in db? + // Call only after remaining_padding_ has been set (for padded frames), or + // been cleared (for unpadded frames); and after all of the non-padding + // payload has been decoded. + size_t AvailablePadding(DecodeBuffer* db) const { + DCHECK(IsPaddable()) << frame_header(); + DCHECK_EQ(remaining_payload_, 0u); + return db->MinLengthRemaining(remaining_padding_); + } + + // Reduces remaining_payload_ by amount. To be called by a payload decoder + // after it has passed a variable length portion of the payload to the + // listener; remaining_payload_ will be automatically reduced when fixed + // size structures and padding, including the Pad Length field, are decoded. + void ConsumePayload(size_t amount) { + DCHECK_LE(amount, remaining_payload_); + remaining_payload_ -= amount; + } + + // Reads the Pad Length field into remaining_padding_, and appropriately sets + // remaining_payload_. When present, the Pad Length field is always the first + // field in the payload, which this method relies on so that the caller need + // not set remaining_payload_ before calling this method. + // If report_pad_length is true, calls the listener's OnPadLength method when + // it decodes the Pad Length field. + // Returns kDecodeDone if the decode buffer was not empty (i.e. because the + // field is only a single byte long, it can always be decoded if the buffer is + // not empty). + // Returns kDecodeError if the buffer is empty because the frame has no + // payload (i.e. payload_length() == 0). + // Returns kDecodeInProgress if the buffer is empty but the frame has a + // payload. + DecodeStatus ReadPadLength(DecodeBuffer* db, bool report_pad_length); + + // Skip the trailing padding bytes; only call once remaining_payload_==0. + // Returns true when the padding has been skipped. + // Does NOT check that the padding is all zeroes. + bool SkipPadding(DecodeBuffer* db); + + // Calls the listener's OnFrameSizeError method and returns kDecodeError. + DecodeStatus ReportFrameSizeError(); + + private: + friend class Http2FrameDecoder; + friend class test::FrameDecoderStatePeer; + + // Starts the decoding of a common frame header. Returns true if completed the + // decoding, false if the decode buffer didn't have enough data in it, in + // which case the decode buffer will have been drained and the caller should + // call ResumeDecodingFrameHeader when more data is available. This is called + // from Http2FrameDecoder, a friend class. + bool StartDecodingFrameHeader(DecodeBuffer* db) { + return structure_decoder_.Start(&frame_header_, db); + } + + // Resumes decoding the common frame header after the preceding call to + // StartDecodingFrameHeader returned false, as did any subsequent calls to + // ResumeDecodingFrameHeader. This is called from Http2FrameDecoder, + // a friend class. + bool ResumeDecodingFrameHeader(DecodeBuffer* db) { + return structure_decoder_.Resume(&frame_header_, db); + } + + // Clear any of the flags in the frame header that aren't set in valid_flags. + void RetainFlags(uint8_t valid_flags) { + frame_header_.RetainFlags(valid_flags); + } + + // Clear all of the flags in the frame header; for use with frame types that + // don't define any flags, such as WINDOW_UPDATE. + void ClearFlags() { frame_header_.flags = Http2FrameFlag(); } + + // Returns true if the type of frame being decoded can have padding. + bool IsPaddable() const { + return frame_header().type == Http2FrameType::DATA || + frame_header().type == Http2FrameType::HEADERS || + frame_header().type == Http2FrameType::PUSH_PROMISE; + } + + Http2FrameDecoderListener* listener_ = nullptr; + Http2FrameHeader frame_header_; + + // Number of bytes remaining to be decoded, if set; does not include the + // trailing padding once the length of padding has been determined. + // See ReadPadLength. + uint32_t remaining_payload_; + + // The amount of trailing padding after the payload that remains to be + // decoded. See ReadPadLength. + uint32_t remaining_padding_; + + // Generic decoder of structures, which takes care of buffering the needed + // bytes if the encoded structure is split across decode buffers. + Http2StructureDecoder structure_decoder_; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_DECODER_FRAME_DECODER_STATE_H_
diff --git a/http2/decoder/frame_decoder_state_test_util.cc b/http2/decoder/frame_decoder_state_test_util.cc new file mode 100644 index 0000000..7c13ef0 --- /dev/null +++ b/http2/decoder/frame_decoder_state_test_util.cc
@@ -0,0 +1,34 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state_test_util.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test_util.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/http2_structures_test_util.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +namespace http2 { +namespace test { + +// static +void FrameDecoderStatePeer::Randomize(FrameDecoderState* p, Http2Random* rng) { + VLOG(1) << "FrameDecoderStatePeer::Randomize"; + ::http2::test::Randomize(&p->frame_header_, rng); + p->remaining_payload_ = rng->Rand32(); + p->remaining_padding_ = rng->Rand32(); + Http2StructureDecoderPeer::Randomize(&p->structure_decoder_, rng); +} + +// static +void FrameDecoderStatePeer::set_frame_header(const Http2FrameHeader& header, + FrameDecoderState* p) { + VLOG(1) << "FrameDecoderStatePeer::set_frame_header " << header; + p->frame_header_ = header; +} + +} // namespace test +} // namespace http2
diff --git a/http2/decoder/frame_decoder_state_test_util.h b/http2/decoder/frame_decoder_state_test_util.h new file mode 100644 index 0000000..fc81a4a --- /dev/null +++ b/http2/decoder/frame_decoder_state_test_util.h
@@ -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. + +#ifndef QUICHE_HTTP2_DECODER_FRAME_DECODER_STATE_TEST_UTIL_H_ +#define QUICHE_HTTP2_DECODER_FRAME_DECODER_STATE_TEST_UTIL_H_ + +#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +namespace http2 { +namespace test { + +class FrameDecoderStatePeer { + public: + // Randomizes (i.e. corrupts) the fields of the FrameDecoderState. + // PayloadDecoderBaseTest::StartDecoding calls this before passing the first + // decode buffer to the payload decoder, which increases the likelihood of + // detecting any use of prior states of the decoder on the decoding of + // future payloads. + static void Randomize(FrameDecoderState* p, Http2Random* rng); + + // Inject a frame header into the FrameDecoderState. + // PayloadDecoderBaseTest::StartDecoding calls this just after calling + // Randomize (above), to simulate a full frame decoder having just finished + // decoding the common frame header and then calling the appropriate payload + // decoder based on the frame type in that frame header. + static void set_frame_header(const Http2FrameHeader& header, + FrameDecoderState* p); +}; + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_DECODER_FRAME_DECODER_STATE_TEST_UTIL_H_
diff --git a/http2/decoder/http2_frame_decoder.cc b/http2/decoder/http2_frame_decoder.cc new file mode 100644 index 0000000..314dfbf --- /dev/null +++ b/http2/decoder/http2_frame_decoder.cc
@@ -0,0 +1,433 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder.h" + +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h" + +namespace http2 { + +std::ostream& operator<<(std::ostream& out, Http2FrameDecoder::State v) { + switch (v) { + case Http2FrameDecoder::State::kStartDecodingHeader: + return out << "kStartDecodingHeader"; + case Http2FrameDecoder::State::kResumeDecodingHeader: + return out << "kResumeDecodingHeader"; + case Http2FrameDecoder::State::kResumeDecodingPayload: + return out << "kResumeDecodingPayload"; + case Http2FrameDecoder::State::kDiscardPayload: + return out << "kDiscardPayload"; + } + // 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 << "Http2FrameDecoder::State " << unknown; + return out << "Http2FrameDecoder::State(" << unknown << ")"; +} + +Http2FrameDecoder::Http2FrameDecoder(Http2FrameDecoderListener* listener) + : state_(State::kStartDecodingHeader), + maximum_payload_size_(Http2SettingsInfo::DefaultMaxFrameSize()) { + set_listener(listener); +} + +void Http2FrameDecoder::set_listener(Http2FrameDecoderListener* listener) { + if (listener == nullptr) { + listener = &no_op_listener_; + } + frame_decoder_state_.set_listener(listener); +} + +Http2FrameDecoderListener* Http2FrameDecoder::listener() const { + return frame_decoder_state_.listener(); +} + +DecodeStatus Http2FrameDecoder::DecodeFrame(DecodeBuffer* db) { + DVLOG(2) << "Http2FrameDecoder::DecodeFrame state=" << state_; + switch (state_) { + case State::kStartDecodingHeader: + if (frame_decoder_state_.StartDecodingFrameHeader(db)) { + return StartDecodingPayload(db); + } + state_ = State::kResumeDecodingHeader; + return DecodeStatus::kDecodeInProgress; + + case State::kResumeDecodingHeader: + if (frame_decoder_state_.ResumeDecodingFrameHeader(db)) { + return StartDecodingPayload(db); + } + return DecodeStatus::kDecodeInProgress; + + case State::kResumeDecodingPayload: + return ResumeDecodingPayload(db); + + case State::kDiscardPayload: + return DiscardPayload(db); + } + + HTTP2_UNREACHABLE(); + return DecodeStatus::kDecodeError; +} + +size_t Http2FrameDecoder::remaining_payload() const { + return frame_decoder_state_.remaining_payload(); +} + +uint32_t Http2FrameDecoder::remaining_padding() const { + return frame_decoder_state_.remaining_padding(); +} + +DecodeStatus Http2FrameDecoder::StartDecodingPayload(DecodeBuffer* db) { + const Http2FrameHeader& header = frame_header(); + + // TODO(jamessynge): Remove OnFrameHeader once done with supporting + // SpdyFramer's exact states. + if (!listener()->OnFrameHeader(header)) { + DVLOG(2) << "OnFrameHeader rejected the frame, will discard; header: " + << header; + state_ = State::kDiscardPayload; + frame_decoder_state_.InitializeRemainders(); + return DecodeStatus::kDecodeError; + } + + if (header.payload_length > maximum_payload_size_) { + DVLOG(2) << "Payload length is greater than allowed: " + << header.payload_length << " > " << maximum_payload_size_ + << "\n header: " << header; + state_ = State::kDiscardPayload; + frame_decoder_state_.InitializeRemainders(); + listener()->OnFrameSizeError(header); + return DecodeStatus::kDecodeError; + } + + // The decode buffer can extend across many frames. Make sure that the + // buffer we pass to the start method that is specific to the frame type + // does not exend beyond this frame. + DecodeBufferSubset subset(db, header.payload_length); + DecodeStatus status; + switch (header.type) { + case Http2FrameType::DATA: + status = StartDecodingDataPayload(&subset); + break; + + case Http2FrameType::HEADERS: + status = StartDecodingHeadersPayload(&subset); + break; + + case Http2FrameType::PRIORITY: + status = StartDecodingPriorityPayload(&subset); + break; + + case Http2FrameType::RST_STREAM: + status = StartDecodingRstStreamPayload(&subset); + break; + + case Http2FrameType::SETTINGS: + status = StartDecodingSettingsPayload(&subset); + break; + + case Http2FrameType::PUSH_PROMISE: + status = StartDecodingPushPromisePayload(&subset); + break; + + case Http2FrameType::PING: + status = StartDecodingPingPayload(&subset); + break; + + case Http2FrameType::GOAWAY: + status = StartDecodingGoAwayPayload(&subset); + break; + + case Http2FrameType::WINDOW_UPDATE: + status = StartDecodingWindowUpdatePayload(&subset); + break; + + case Http2FrameType::CONTINUATION: + status = StartDecodingContinuationPayload(&subset); + break; + + case Http2FrameType::ALTSVC: + status = StartDecodingAltSvcPayload(&subset); + break; + + default: + status = StartDecodingUnknownPayload(&subset); + break; + } + + if (status == DecodeStatus::kDecodeDone) { + state_ = State::kStartDecodingHeader; + return status; + } else if (status == DecodeStatus::kDecodeInProgress) { + state_ = State::kResumeDecodingPayload; + return status; + } else { + state_ = State::kDiscardPayload; + return status; + } +} + +DecodeStatus Http2FrameDecoder::ResumeDecodingPayload(DecodeBuffer* db) { + // The decode buffer can extend across many frames. Make sure that the + // buffer we pass to the start method that is specific to the frame type + // does not exend beyond this frame. + size_t remaining = frame_decoder_state_.remaining_total_payload(); + DCHECK_LE(remaining, frame_header().payload_length); + DecodeBufferSubset subset(db, remaining); + DecodeStatus status; + switch (frame_header().type) { + case Http2FrameType::DATA: + status = ResumeDecodingDataPayload(&subset); + break; + + case Http2FrameType::HEADERS: + status = ResumeDecodingHeadersPayload(&subset); + break; + + case Http2FrameType::PRIORITY: + status = ResumeDecodingPriorityPayload(&subset); + break; + + case Http2FrameType::RST_STREAM: + status = ResumeDecodingRstStreamPayload(&subset); + break; + + case Http2FrameType::SETTINGS: + status = ResumeDecodingSettingsPayload(&subset); + break; + + case Http2FrameType::PUSH_PROMISE: + status = ResumeDecodingPushPromisePayload(&subset); + break; + + case Http2FrameType::PING: + status = ResumeDecodingPingPayload(&subset); + break; + + case Http2FrameType::GOAWAY: + status = ResumeDecodingGoAwayPayload(&subset); + break; + + case Http2FrameType::WINDOW_UPDATE: + status = ResumeDecodingWindowUpdatePayload(&subset); + break; + + case Http2FrameType::CONTINUATION: + status = ResumeDecodingContinuationPayload(&subset); + break; + + case Http2FrameType::ALTSVC: + status = ResumeDecodingAltSvcPayload(&subset); + break; + + default: + status = ResumeDecodingUnknownPayload(&subset); + break; + } + + if (status == DecodeStatus::kDecodeDone) { + state_ = State::kStartDecodingHeader; + return status; + } else if (status == DecodeStatus::kDecodeInProgress) { + return status; + } else { + state_ = State::kDiscardPayload; + return status; + } +} + +// Clear any of the flags in the frame header that aren't set in valid_flags. +void Http2FrameDecoder::RetainFlags(uint8_t valid_flags) { + frame_decoder_state_.RetainFlags(valid_flags); +} + +// Clear all of the flags in the frame header; for use with frame types that +// don't define any flags, such as WINDOW_UPDATE. +void Http2FrameDecoder::ClearFlags() { + frame_decoder_state_.ClearFlags(); +} + +DecodeStatus Http2FrameDecoder::StartDecodingAltSvcPayload(DecodeBuffer* db) { + ClearFlags(); + return altsvc_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, + db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingAltSvcPayload(DecodeBuffer* db) { + // The frame is not paddable. + DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), + frame_decoder_state_.remaining_payload()); + return altsvc_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, + db); +} + +DecodeStatus Http2FrameDecoder::StartDecodingContinuationPayload( + DecodeBuffer* db) { + RetainFlags(Http2FrameFlag::END_HEADERS); + return continuation_payload_decoder_.StartDecodingPayload( + &frame_decoder_state_, db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingContinuationPayload( + DecodeBuffer* db) { + // The frame is not paddable. + DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), + frame_decoder_state_.remaining_payload()); + return continuation_payload_decoder_.ResumeDecodingPayload( + &frame_decoder_state_, db); +} + +DecodeStatus Http2FrameDecoder::StartDecodingDataPayload(DecodeBuffer* db) { + RetainFlags(Http2FrameFlag::END_STREAM | Http2FrameFlag::PADDED); + return data_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingDataPayload(DecodeBuffer* db) { + return data_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, db); +} + +DecodeStatus Http2FrameDecoder::StartDecodingGoAwayPayload(DecodeBuffer* db) { + ClearFlags(); + return goaway_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, + db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingGoAwayPayload(DecodeBuffer* db) { + // The frame is not paddable. + DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), + frame_decoder_state_.remaining_payload()); + return goaway_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, + db); +} + +DecodeStatus Http2FrameDecoder::StartDecodingHeadersPayload(DecodeBuffer* db) { + RetainFlags(Http2FrameFlag::END_STREAM | Http2FrameFlag::END_HEADERS | + Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY); + return headers_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, + db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingHeadersPayload(DecodeBuffer* db) { + DCHECK_LE(frame_decoder_state_.remaining_payload_and_padding(), + frame_header().payload_length); + return headers_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, + db); +} + +DecodeStatus Http2FrameDecoder::StartDecodingPingPayload(DecodeBuffer* db) { + RetainFlags(Http2FrameFlag::ACK); + return ping_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingPingPayload(DecodeBuffer* db) { + // The frame is not paddable. + DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), + frame_decoder_state_.remaining_payload()); + return ping_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, db); +} + +DecodeStatus Http2FrameDecoder::StartDecodingPriorityPayload(DecodeBuffer* db) { + ClearFlags(); + return priority_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, + db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingPriorityPayload( + DecodeBuffer* db) { + // The frame is not paddable. + DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), + frame_decoder_state_.remaining_payload()); + return priority_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, + db); +} + +DecodeStatus Http2FrameDecoder::StartDecodingPushPromisePayload( + DecodeBuffer* db) { + RetainFlags(Http2FrameFlag::END_HEADERS | Http2FrameFlag::PADDED); + return push_promise_payload_decoder_.StartDecodingPayload( + &frame_decoder_state_, db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingPushPromisePayload( + DecodeBuffer* db) { + DCHECK_LE(frame_decoder_state_.remaining_payload_and_padding(), + frame_header().payload_length); + return push_promise_payload_decoder_.ResumeDecodingPayload( + &frame_decoder_state_, db); +} + +DecodeStatus Http2FrameDecoder::StartDecodingRstStreamPayload( + DecodeBuffer* db) { + ClearFlags(); + return rst_stream_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, + db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingRstStreamPayload( + DecodeBuffer* db) { + // The frame is not paddable. + DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), + frame_decoder_state_.remaining_payload()); + return rst_stream_payload_decoder_.ResumeDecodingPayload( + &frame_decoder_state_, db); +} + +DecodeStatus Http2FrameDecoder::StartDecodingSettingsPayload(DecodeBuffer* db) { + RetainFlags(Http2FrameFlag::ACK); + return settings_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, + db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingSettingsPayload( + DecodeBuffer* db) { + // The frame is not paddable. + DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), + frame_decoder_state_.remaining_payload()); + return settings_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, + db); +} + +DecodeStatus Http2FrameDecoder::StartDecodingUnknownPayload(DecodeBuffer* db) { + // We don't known what type of frame this is, so we don't know which flags + // are valid, so we don't touch them. + return unknown_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, + db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingUnknownPayload(DecodeBuffer* db) { + // We don't known what type of frame this is, so we treat it as not paddable. + DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), + frame_decoder_state_.remaining_payload()); + return unknown_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, + db); +} + +DecodeStatus Http2FrameDecoder::StartDecodingWindowUpdatePayload( + DecodeBuffer* db) { + ClearFlags(); + return window_update_payload_decoder_.StartDecodingPayload( + &frame_decoder_state_, db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingWindowUpdatePayload( + DecodeBuffer* db) { + // The frame is not paddable. + DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), + frame_decoder_state_.remaining_payload()); + return window_update_payload_decoder_.ResumeDecodingPayload( + &frame_decoder_state_, db); +} + +DecodeStatus Http2FrameDecoder::DiscardPayload(DecodeBuffer* db) { + DVLOG(2) << "remaining_payload=" << frame_decoder_state_.remaining_payload_ + << "; remaining_padding=" << frame_decoder_state_.remaining_padding_; + frame_decoder_state_.remaining_payload_ += + frame_decoder_state_.remaining_padding_; + frame_decoder_state_.remaining_padding_ = 0; + const size_t avail = frame_decoder_state_.AvailablePayload(db); + DVLOG(2) << "avail=" << avail; + if (avail > 0) { + frame_decoder_state_.ConsumePayload(avail); + db->AdvanceCursor(avail); + } + if (frame_decoder_state_.remaining_payload_ == 0) { + state_ = State::kStartDecodingHeader; + return DecodeStatus::kDecodeDone; + } + return DecodeStatus::kDecodeInProgress; +} + +} // namespace http2
diff --git a/http2/decoder/http2_frame_decoder.h b/http2/decoder/http2_frame_decoder.h new file mode 100644 index 0000000..6257bbe --- /dev/null +++ b/http2/decoder/http2_frame_decoder.h
@@ -0,0 +1,205 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_H_ +#define QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_H_ + +// Http2FrameDecoder decodes the available input until it reaches the end of +// the input or it reaches the end of the first frame in the input. +// Note that Http2FrameDecoder does only minimal validation; for example, +// stream ids are not checked, nor is the sequence of frames such as +// CONTINUATION frame placement. +// +// Http2FrameDecoder enters state kError once it has called the listener's +// OnFrameSizeError or OnPaddingTooLong methods, and at this time has no +// provision for leaving that state. While the HTTP/2 spec (RFC7540) allows +// for some such errors to be considered as just stream errors in some cases, +// this implementation treats them all as connection errors. + +#include <stddef.h> + +#include <cstdint> + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" + +namespace http2 { +namespace test { +class Http2FrameDecoderPeer; +} // namespace test + +class HTTP2_EXPORT_PRIVATE Http2FrameDecoder { + public: + explicit Http2FrameDecoder(Http2FrameDecoderListener* listener); + Http2FrameDecoder() : Http2FrameDecoder(nullptr) {} + + Http2FrameDecoder(const Http2FrameDecoder&) = delete; + Http2FrameDecoder& operator=(const Http2FrameDecoder&) = delete; + + // The decoder will call the listener's methods as it decodes a frame. + void set_listener(Http2FrameDecoderListener* listener); + Http2FrameDecoderListener* listener() const; + + // The decoder will reject frame's whose payload + // length field exceeds the maximum payload size. + void set_maximum_payload_size(size_t v) { maximum_payload_size_ = v; } + size_t maximum_payload_size() const { return maximum_payload_size_; } + + // Decodes the input up to the next frame boundary (i.e. at most one frame). + // + // Returns kDecodeDone if it decodes the final byte of a frame, OR if there + // is no input and it is awaiting the start of a new frame (e.g. if this + // is the first call to DecodeFrame, or if the previous call returned + // kDecodeDone). + // + // Returns kDecodeInProgress if it decodes all of the decode buffer, but has + // not reached the end of the frame. + // + // Returns kDecodeError if the frame's padding or length wasn't valid (i.e. if + // the decoder called either the listener's OnPaddingTooLong or + // OnFrameSizeError method). + DecodeStatus DecodeFrame(DecodeBuffer* db); + + ////////////////////////////////////////////////////////////////////////////// + // Methods that support Http2FrameDecoderAdapter. + + // Is the remainder of the frame's payload being discarded? + bool IsDiscardingPayload() const { return state_ == State::kDiscardPayload; } + + // Returns the number of bytes of the frame's payload that remain to be + // decoded, excluding any trailing padding. This method must only be called + // after the frame header has been decoded AND DecodeFrame has returned + // kDecodeInProgress. + size_t remaining_payload() const; + + // Returns the number of bytes of trailing padding after the payload that + // remain to be decoded. This method must only be called if the frame type + // allows padding, and after the frame header has been decoded AND + // DecodeFrame has returned. Will return 0 if the Pad Length field has not + // yet been decoded. + uint32_t remaining_padding() const; + + private: + enum class State { + // Ready to start decoding a new frame's header. + kStartDecodingHeader, + // Was in state kStartDecodingHeader, but unable to read the entire frame + // header, so needs more input to complete decoding the header. + kResumeDecodingHeader, + + // Have decoded the frame header, and started decoding the available bytes + // of the frame's payload, but need more bytes to finish the job. + kResumeDecodingPayload, + + // Decoding of the most recently started frame resulted in an error: + // OnPaddingTooLong or OnFrameSizeError was called to indicate that the + // decoder detected a problem, or OnFrameHeader returned false, indicating + // that the listener detected a problem. Regardless of which, the decoder + // will stay in state kDiscardPayload until it has been passed the rest + // of the bytes of the frame's payload that it hasn't yet seen, after + // which it will be ready to decode another frame. + kDiscardPayload, + }; + + friend class test::Http2FrameDecoderPeer; + friend std::ostream& operator<<(std::ostream& out, State v); + + DecodeStatus StartDecodingPayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingPayload(DecodeBuffer* db); + DecodeStatus DiscardPayload(DecodeBuffer* db); + + const Http2FrameHeader& frame_header() const { + return frame_decoder_state_.frame_header(); + } + + // Clear any of the flags in the frame header that aren't set in valid_flags. + void RetainFlags(uint8_t valid_flags); + + // Clear all of the flags in the frame header; for use with frame types that + // don't define any flags, such as WINDOW_UPDATE. + void ClearFlags(); + + // These methods call the StartDecodingPayload() method of the frame type's + // payload decoder, after first clearing invalid flags in the header. The + // caller must ensure that the decode buffer does not extend beyond the + // end of the payload (handled by Http2FrameDecoder::StartDecodingPayload). + DecodeStatus StartDecodingAltSvcPayload(DecodeBuffer* db); + DecodeStatus StartDecodingContinuationPayload(DecodeBuffer* db); + DecodeStatus StartDecodingDataPayload(DecodeBuffer* db); + DecodeStatus StartDecodingGoAwayPayload(DecodeBuffer* db); + DecodeStatus StartDecodingHeadersPayload(DecodeBuffer* db); + DecodeStatus StartDecodingPingPayload(DecodeBuffer* db); + DecodeStatus StartDecodingPriorityPayload(DecodeBuffer* db); + DecodeStatus StartDecodingPushPromisePayload(DecodeBuffer* db); + DecodeStatus StartDecodingRstStreamPayload(DecodeBuffer* db); + DecodeStatus StartDecodingSettingsPayload(DecodeBuffer* db); + DecodeStatus StartDecodingUnknownPayload(DecodeBuffer* db); + DecodeStatus StartDecodingWindowUpdatePayload(DecodeBuffer* db); + + // These methods call the ResumeDecodingPayload() method of the frame type's + // payload decoder; they are called only if the preceding call to the + // corresponding Start method (above) returned kDecodeInProgress, as did any + // subsequent calls to the resume method. + // Unlike the Start methods, the decode buffer may extend beyond the + // end of the payload, so the method will create a DecodeBufferSubset + // before calling the ResumeDecodingPayload method of the frame type's + // payload decoder. + DecodeStatus ResumeDecodingAltSvcPayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingContinuationPayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingDataPayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingGoAwayPayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingHeadersPayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingPingPayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingPriorityPayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingPushPromisePayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingRstStreamPayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingSettingsPayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingUnknownPayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingWindowUpdatePayload(DecodeBuffer* db); + + FrameDecoderState frame_decoder_state_; + + // We only need one payload decoder at a time, so they share the same storage. + union { + AltSvcPayloadDecoder altsvc_payload_decoder_; + ContinuationPayloadDecoder continuation_payload_decoder_; + DataPayloadDecoder data_payload_decoder_; + GoAwayPayloadDecoder goaway_payload_decoder_; + HeadersPayloadDecoder headers_payload_decoder_; + PingPayloadDecoder ping_payload_decoder_; + PriorityPayloadDecoder priority_payload_decoder_; + PushPromisePayloadDecoder push_promise_payload_decoder_; + RstStreamPayloadDecoder rst_stream_payload_decoder_; + SettingsPayloadDecoder settings_payload_decoder_; + UnknownPayloadDecoder unknown_payload_decoder_; + WindowUpdatePayloadDecoder window_update_payload_decoder_; + }; + + State state_; + size_t maximum_payload_size_; + + // Listener used whenever caller passes nullptr to set_listener. + Http2FrameDecoderNoOpListener no_op_listener_; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_H_
diff --git a/http2/decoder/http2_frame_decoder_listener.cc b/http2/decoder/http2_frame_decoder_listener.cc new file mode 100644 index 0000000..4cbce66 --- /dev/null +++ b/http2/decoder/http2_frame_decoder_listener.cc
@@ -0,0 +1,14 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" + +namespace http2 { + +bool Http2FrameDecoderNoOpListener::OnFrameHeader( + const Http2FrameHeader& header) { + return true; +} + +} // namespace http2
diff --git a/http2/decoder/http2_frame_decoder_listener.h b/http2/decoder/http2_frame_decoder_listener.h new file mode 100644 index 0000000..b875122 --- /dev/null +++ b/http2/decoder/http2_frame_decoder_listener.h
@@ -0,0 +1,357 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_LISTENER_H_ +#define QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_LISTENER_H_ + +// Http2FrameDecoderListener is the interface which the HTTP/2 decoder uses +// to report the decoded frames to a listener. +// +// The general design is to assume that the listener will copy the data it needs +// (e.g. frame headers) and will keep track of the implicit state of the +// decoding process (i.e. the decoder maintains just the information it needs in +// order to perform the decoding). Therefore, the parameters are just those with +// (potentially) new data, not previously provided info about the current frame. +// +// The calls are described as if they are made in quick succession, i.e. one +// after another, but of course the decoder needs input to decode, and the +// decoder will only call the listener once the necessary input has been +// provided. For example: OnDataStart can only be called once the 9 bytes of +// of an HTTP/2 common frame header have been received. The decoder will call +// the listener methods as soon as possible to avoid almost all buffering. +// +// The listener interface is designed so that it is possible to exactly +// reconstruct the serialized frames, with the exception of reserved bits, +// including in the frame header's flags and stream_id fields, which will have +// been cleared before the methods below are called. + +#include <stddef.h> + +#include <cstdint> +#include <type_traits> + +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" + +namespace http2 { + +// TODO(jamessynge): Consider sorting the methods by frequency of call, if that +// helps at all. +class Http2FrameDecoderListener { + public: + Http2FrameDecoderListener() {} + virtual ~Http2FrameDecoderListener() {} + + // Called once the common frame header has been decoded for any frame, and + // before any of the methods below, which will also be called. This method is + // included in this interface only for the purpose of supporting SpdyFramer + // semantics via an adapter. This is the only method that has a non-void + // return type, and this is just so that Http2FrameDecoderAdapter (called + // from SpdyFramer) can more readily pass existing tests that expect decoding + // to stop if the headers alone indicate an error. Return false to stop + // decoding just after decoding the header, else return true to continue + // decoding. + // TODO(jamessynge): Remove OnFrameHeader once done with supporting + // SpdyFramer's exact states. + virtual bool OnFrameHeader(const Http2FrameHeader& header) = 0; + + ////////////////////////////////////////////////////////////////////////////// + + // Called once the common frame header has been decoded for a DATA frame, + // before examining the frame's payload, after which: + // OnPadLength will be called if header.IsPadded() is true, i.e. if the + // PADDED flag is set; + // OnDataPayload will be called as the non-padding portion of the payload + // is available until all of it has been provided; + // OnPadding will be called if the frame is padded AND the Pad Length field + // is greater than zero; + // OnDataEnd will be called last. If the frame is unpadded and has no + // payload, then this will be called immediately after OnDataStart. + virtual void OnDataStart(const Http2FrameHeader& header) = 0; + + // Called when the next non-padding portion of a DATA frame's payload is + // received. + // |data| The start of |len| bytes of data. + // |len| The length of the data buffer. Maybe zero in some cases, which does + // not mean anything special. + virtual void OnDataPayload(const char* data, size_t len) = 0; + + // Called after an entire DATA frame has been received. + // If header.IsEndStream() == true, this is the last data for the stream. + virtual void OnDataEnd() = 0; + + // Called once the common frame header has been decoded for a HEADERS frame, + // before examining the frame's payload, after which: + // OnPadLength will be called if header.IsPadded() is true, i.e. if the + // PADDED flag is set; + // OnHeadersPriority will be called if header.HasPriority() is true, i.e. if + // the frame has the PRIORITY flag; + // OnHpackFragment as the remainder of the non-padding payload is available + // until all if has been provided; + // OnPadding will be called if the frame is padded AND the Pad Length field + // is greater than zero; + // OnHeadersEnd will be called last; If the frame is unpadded and has no + // payload, then this will be called immediately after OnHeadersStart; + // OnHeadersEnd indicates the end of the HPACK block only if the frame + // header had the END_HEADERS flag set, else the END_HEADERS should be + // looked for on a subsequent CONTINUATION frame. + virtual void OnHeadersStart(const Http2FrameHeader& header) = 0; + + // Called when a HEADERS frame is received with the PRIORITY flag set and + // the priority fields have been decoded. + virtual void OnHeadersPriority( + const Http2PriorityFields& priority_fields) = 0; + + // Called when a fragment (i.e. some or all of an HPACK Block) is received; + // this may be part of a HEADERS, PUSH_PROMISE or CONTINUATION frame. + // |data| The start of |len| bytes of data. + // |len| The length of the data buffer. Maybe zero in some cases, which does + // not mean anything special, except that it simplified the decoder. + virtual void OnHpackFragment(const char* data, size_t len) = 0; + + // Called after an entire HEADERS frame has been received. The frame is the + // end of the HEADERS if the END_HEADERS flag is set; else there should be + // CONTINUATION frames after this frame. + virtual void OnHeadersEnd() = 0; + + // Called when an entire PRIORITY frame has been decoded. + virtual void OnPriorityFrame(const Http2FrameHeader& header, + const Http2PriorityFields& priority_fields) = 0; + + // Called once the common frame header has been decoded for a CONTINUATION + // frame, before examining the frame's payload, after which: + // OnHpackFragment as the frame's payload is available until all of it + // has been provided; + // OnContinuationEnd will be called last; If the frame has no payload, + // then this will be called immediately after OnContinuationStart; + // the HPACK block is at an end if and only if the frame header passed + // to OnContinuationStart had the END_HEADERS flag set. + virtual void OnContinuationStart(const Http2FrameHeader& header) = 0; + + // Called after an entire CONTINUATION frame has been received. The frame is + // the end of the HEADERS if the END_HEADERS flag is set. + virtual void OnContinuationEnd() = 0; + + // Called when Pad Length field has been read. Applies to DATA and HEADERS + // frames. For PUSH_PROMISE frames, the Pad Length + 1 is provided in the + // OnPushPromiseStart call as total_padding_length. + virtual void OnPadLength(size_t pad_length) = 0; + + // Called when padding is skipped over. + virtual void OnPadding(const char* padding, size_t skipped_length) = 0; + + // Called when an entire RST_STREAM frame has been decoded. + // This is the only callback for RST_STREAM frames. + virtual void OnRstStream(const Http2FrameHeader& header, + Http2ErrorCode error_code) = 0; + + // Called once the common frame header has been decoded for a SETTINGS frame + // without the ACK flag, before examining the frame's payload, after which: + // OnSetting will be called in turn for each pair of settings parameter and + // value found in the payload; + // OnSettingsEnd will be called last; If the frame has no payload, + // then this will be called immediately after OnSettingsStart. + // The frame header is passed so that the caller can check the stream_id, + // which should be zero, but that hasn't been checked by the decoder. + virtual void OnSettingsStart(const Http2FrameHeader& header) = 0; + + // Called for each setting parameter and value within a SETTINGS frame. + virtual void OnSetting(const Http2SettingFields& setting_fields) = 0; + + // Called after parsing the complete payload of SETTINGS frame (non-ACK). + virtual void OnSettingsEnd() = 0; + + // Called when an entire SETTINGS frame, with the ACK flag, has been decoded. + virtual void OnSettingsAck(const Http2FrameHeader& header) = 0; + + // Called just before starting to process the HPACK block of a PUSH_PROMISE + // frame. The Pad Length field has already been decoded at this point, so + // OnPadLength will not be called; note that total_padding_length is Pad + // Length + 1. After OnPushPromiseStart: + // OnHpackFragment as the remainder of the non-padding payload is available + // until all if has been provided; + // OnPadding will be called if the frame is padded AND the Pad Length field + // is greater than zero (i.e. total_padding_length > 1); + // OnPushPromiseEnd will be called last; If the frame is unpadded and has no + // payload, then this will be called immediately after OnPushPromiseStart. + virtual void OnPushPromiseStart(const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) = 0; + + // Called after all of the HPACK block fragment and padding of a PUSH_PROMISE + // has been decoded and delivered to the listener. This call indicates the end + // of the HPACK block if and only if the frame header had the END_HEADERS flag + // set (i.e. header.IsEndHeaders() is true); otherwise the next block must be + // a CONTINUATION frame with the same stream id (not the same promised stream + // id). + virtual void OnPushPromiseEnd() = 0; + + // Called when an entire PING frame, without the ACK flag, has been decoded. + virtual void OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) = 0; + + // Called when an entire PING frame, with the ACK flag, has been decoded. + virtual void OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) = 0; + + // Called after parsing a GOAWAY frame's header and fixed size fields, after + // which: + // OnGoAwayOpaqueData will be called as opaque data of the payload becomes + // available to the decoder, until all of it has been provided to the + // listener; + // OnGoAwayEnd will be called last, after all the opaque data has been + // provided to the listener; if there is no opaque data, then OnGoAwayEnd + // will be called immediately after OnGoAwayStart. + virtual void OnGoAwayStart(const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) = 0; + + // Called when the next portion of a GOAWAY frame's payload is received. + // |data| The start of |len| bytes of opaque data. + // |len| The length of the opaque data buffer. Maybe zero in some cases, + // which does not mean anything special. + virtual void OnGoAwayOpaqueData(const char* data, size_t len) = 0; + + // Called after finishing decoding all of a GOAWAY frame. + virtual void OnGoAwayEnd() = 0; + + // Called when an entire WINDOW_UPDATE frame has been decoded. The + // window_size_increment is required to be non-zero, but that has not been + // checked. If header.stream_id==0, the connection's flow control window is + // being increased, else the specified stream's flow control is being + // increased. + virtual void OnWindowUpdate(const Http2FrameHeader& header, + uint32_t window_size_increment) = 0; + + // Called when an ALTSVC frame header and origin length have been parsed. + // Either or both lengths may be zero. After OnAltSvcStart: + // OnAltSvcOriginData will be called until all of the (optional) Origin + // has been provided; + // OnAltSvcValueData will be called until all of the Alt-Svc-Field-Value + // has been provided; + // OnAltSvcEnd will called last, after all of the origin and + // Alt-Svc-Field-Value have been delivered to the listener. + virtual void OnAltSvcStart(const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) = 0; + + // Called when decoding the (optional) origin of an ALTSVC; + // the field is uninterpreted. + virtual void OnAltSvcOriginData(const char* data, size_t len) = 0; + + // Called when decoding the Alt-Svc-Field-Value of an ALTSVC; + // the field is uninterpreted. + virtual void OnAltSvcValueData(const char* data, size_t len) = 0; + + // Called after decoding all of a ALTSVC frame and providing to the listener + // via the above methods. + virtual void OnAltSvcEnd() = 0; + + // Called when the common frame header has been decoded, but the frame type + // is unknown, after which: + // OnUnknownPayload is called as the payload of the frame is provided to the + // decoder, until all of the payload has been decoded; + // OnUnknownEnd will called last, after the entire frame of the unknown type + // has been decoded and provided to the listener. + virtual void OnUnknownStart(const Http2FrameHeader& header) = 0; + + // Called when the payload of an unknown frame type is received. + // |data| A buffer containing the data received. + // |len| The length of the data buffer. + virtual void OnUnknownPayload(const char* data, size_t len) = 0; + + // Called after decoding all of the payload of an unknown frame type. + virtual void OnUnknownEnd() = 0; + + ////////////////////////////////////////////////////////////////////////////// + // Below here are events indicating a problem has been detected during + // decoding (i.e. the received frames are malformed in some way). + + // Padding field (uint8) has a value that is too large (i.e. the amount of + // padding is greater than the remainder of the payload that isn't required). + // From RFC Section 6.1, DATA: + // If the length of the padding is the length of the frame payload or + // greater, the recipient MUST treat this as a connection error + // (Section 5.4.1) of type PROTOCOL_ERROR. + // The same is true for HEADERS and PUSH_PROMISE. + virtual void OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) = 0; + + // Frame size error. Depending upon the effected frame, this may or may not + // require terminating the connection, though that is probably the best thing + // to do. + // From RFC Section 4.2, Frame Size: + // An endpoint MUST send an error code of FRAME_SIZE_ERROR if a frame + // exceeds the size defined in SETTINGS_MAX_FRAME_SIZE, exceeds any limit + // defined for the frame type, or is too small to contain mandatory frame + // data. A frame size error in a frame that could alter the state of the + // the entire connection MUST be treated as a connection error + // (Section 5.4.1); this includes any frame carrying a header block + // (Section 4.3) (that is, HEADERS, PUSH_PROMISE, and CONTINUATION), + // SETTINGS, and any frame with a stream identifier of 0. + virtual void OnFrameSizeError(const Http2FrameHeader& header) = 0; +}; + +// Do nothing for each call. Useful for ignoring a frame that is invalid. +class Http2FrameDecoderNoOpListener : public Http2FrameDecoderListener { + public: + Http2FrameDecoderNoOpListener() {} + ~Http2FrameDecoderNoOpListener() override {} + + // TODO(jamessynge): Remove OnFrameHeader once done with supporting + // SpdyFramer's exact states. + bool OnFrameHeader(const Http2FrameHeader& header) override; + + void OnDataStart(const Http2FrameHeader& header) override {} + void OnDataPayload(const char* data, size_t len) override {} + void OnDataEnd() override {} + void OnHeadersStart(const Http2FrameHeader& header) override {} + void OnHeadersPriority(const Http2PriorityFields& priority) override {} + void OnHpackFragment(const char* data, size_t len) override {} + void OnHeadersEnd() override {} + void OnPriorityFrame(const Http2FrameHeader& header, + const Http2PriorityFields& priority) override {} + void OnContinuationStart(const Http2FrameHeader& header) override {} + void OnContinuationEnd() override {} + void OnPadLength(size_t trailing_length) override {} + void OnPadding(const char* padding, size_t skipped_length) override {} + void OnRstStream(const Http2FrameHeader& header, + Http2ErrorCode error_code) override {} + void OnSettingsStart(const Http2FrameHeader& header) override {} + void OnSetting(const Http2SettingFields& setting_fields) override {} + void OnSettingsEnd() override {} + void OnSettingsAck(const Http2FrameHeader& header) override {} + void OnPushPromiseStart(const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) override {} + void OnPushPromiseEnd() override {} + void OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) override {} + void OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) override {} + void OnGoAwayStart(const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) override {} + void OnGoAwayOpaqueData(const char* data, size_t len) override {} + void OnGoAwayEnd() override {} + void OnWindowUpdate(const Http2FrameHeader& header, + uint32_t increment) override {} + void OnAltSvcStart(const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) override {} + void OnAltSvcOriginData(const char* data, size_t len) override {} + void OnAltSvcValueData(const char* data, size_t len) override {} + void OnAltSvcEnd() override {} + void OnUnknownStart(const Http2FrameHeader& header) override {} + void OnUnknownPayload(const char* data, size_t len) override {} + void OnUnknownEnd() override {} + void OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) override {} + void OnFrameSizeError(const Http2FrameHeader& header) override {} +}; + +static_assert(!std::is_abstract<Http2FrameDecoderNoOpListener>(), + "Http2FrameDecoderNoOpListener ought to be concrete."); + +} // namespace http2 + +#endif // QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_LISTENER_H_
diff --git a/http2/decoder/http2_frame_decoder_listener_test_util.cc b/http2/decoder/http2_frame_decoder_listener_test_util.cc new file mode 100644 index 0000000..bc8cf1d --- /dev/null +++ b/http2/decoder/http2_frame_decoder_listener_test_util.cc
@@ -0,0 +1,488 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener_test_util.h" + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" + +namespace http2 { + +FailingHttp2FrameDecoderListener::FailingHttp2FrameDecoderListener() = default; +FailingHttp2FrameDecoderListener::~FailingHttp2FrameDecoderListener() = default; + +bool FailingHttp2FrameDecoderListener::OnFrameHeader( + const Http2FrameHeader& header) { + ADD_FAILURE() << "OnFrameHeader: " << header; + return false; +} + +void FailingHttp2FrameDecoderListener::OnDataStart( + const Http2FrameHeader& header) { + FAIL() << "OnDataStart: " << header; +} + +void FailingHttp2FrameDecoderListener::OnDataPayload(const char* data, + size_t len) { + FAIL() << "OnDataPayload: len=" << len; +} + +void FailingHttp2FrameDecoderListener::OnDataEnd() { + FAIL() << "OnDataEnd"; +} + +void FailingHttp2FrameDecoderListener::OnHeadersStart( + const Http2FrameHeader& header) { + FAIL() << "OnHeadersStart: " << header; +} + +void FailingHttp2FrameDecoderListener::OnHeadersPriority( + const Http2PriorityFields& priority) { + FAIL() << "OnHeadersPriority: " << priority; +} + +void FailingHttp2FrameDecoderListener::OnHpackFragment(const char* data, + size_t len) { + FAIL() << "OnHpackFragment: len=" << len; +} + +void FailingHttp2FrameDecoderListener::OnHeadersEnd() { + FAIL() << "OnHeadersEnd"; +} + +void FailingHttp2FrameDecoderListener::OnPriorityFrame( + const Http2FrameHeader& header, + const Http2PriorityFields& priority) { + FAIL() << "OnPriorityFrame: " << header << "; priority: " << priority; +} + +void FailingHttp2FrameDecoderListener::OnContinuationStart( + const Http2FrameHeader& header) { + FAIL() << "OnContinuationStart: " << header; +} + +void FailingHttp2FrameDecoderListener::OnContinuationEnd() { + FAIL() << "OnContinuationEnd"; +} + +void FailingHttp2FrameDecoderListener::OnPadLength(size_t trailing_length) { + FAIL() << "OnPadLength: trailing_length=" << trailing_length; +} + +void FailingHttp2FrameDecoderListener::OnPadding(const char* padding, + size_t skipped_length) { + FAIL() << "OnPadding: skipped_length=" << skipped_length; +} + +void FailingHttp2FrameDecoderListener::OnRstStream( + const Http2FrameHeader& header, + Http2ErrorCode error_code) { + FAIL() << "OnRstStream: " << header << "; code=" << error_code; +} + +void FailingHttp2FrameDecoderListener::OnSettingsStart( + const Http2FrameHeader& header) { + FAIL() << "OnSettingsStart: " << header; +} + +void FailingHttp2FrameDecoderListener::OnSetting( + const Http2SettingFields& setting_fields) { + FAIL() << "OnSetting: " << setting_fields; +} + +void FailingHttp2FrameDecoderListener::OnSettingsEnd() { + FAIL() << "OnSettingsEnd"; +} + +void FailingHttp2FrameDecoderListener::OnSettingsAck( + const Http2FrameHeader& header) { + FAIL() << "OnSettingsAck: " << header; +} + +void FailingHttp2FrameDecoderListener::OnPushPromiseStart( + const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) { + FAIL() << "OnPushPromiseStart: " << header << "; promise: " << promise + << "; total_padding_length: " << total_padding_length; +} + +void FailingHttp2FrameDecoderListener::OnPushPromiseEnd() { + FAIL() << "OnPushPromiseEnd"; +} + +void FailingHttp2FrameDecoderListener::OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) { + FAIL() << "OnPing: " << header << "; ping: " << ping; +} + +void FailingHttp2FrameDecoderListener::OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) { + FAIL() << "OnPingAck: " << header << "; ping: " << ping; +} + +void FailingHttp2FrameDecoderListener::OnGoAwayStart( + const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) { + FAIL() << "OnGoAwayStart: " << header << "; goaway: " << goaway; +} + +void FailingHttp2FrameDecoderListener::OnGoAwayOpaqueData(const char* data, + size_t len) { + FAIL() << "OnGoAwayOpaqueData: len=" << len; +} + +void FailingHttp2FrameDecoderListener::OnGoAwayEnd() { + FAIL() << "OnGoAwayEnd"; +} + +void FailingHttp2FrameDecoderListener::OnWindowUpdate( + const Http2FrameHeader& header, + uint32_t increment) { + FAIL() << "OnWindowUpdate: " << header << "; increment=" << increment; +} + +void FailingHttp2FrameDecoderListener::OnAltSvcStart( + const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) { + FAIL() << "OnAltSvcStart: " << header << "; origin_length: " << origin_length + << "; value_length: " << value_length; +} + +void FailingHttp2FrameDecoderListener::OnAltSvcOriginData(const char* data, + size_t len) { + FAIL() << "OnAltSvcOriginData: len=" << len; +} + +void FailingHttp2FrameDecoderListener::OnAltSvcValueData(const char* data, + size_t len) { + FAIL() << "OnAltSvcValueData: len=" << len; +} + +void FailingHttp2FrameDecoderListener::OnAltSvcEnd() { + FAIL() << "OnAltSvcEnd"; +} + +void FailingHttp2FrameDecoderListener::OnUnknownStart( + const Http2FrameHeader& header) { + FAIL() << "OnUnknownStart: " << header; +} + +void FailingHttp2FrameDecoderListener::OnUnknownPayload(const char* data, + size_t len) { + FAIL() << "OnUnknownPayload: len=" << len; +} + +void FailingHttp2FrameDecoderListener::OnUnknownEnd() { + FAIL() << "OnUnknownEnd"; +} + +void FailingHttp2FrameDecoderListener::OnPaddingTooLong( + const Http2FrameHeader& header, + size_t missing_length) { + FAIL() << "OnPaddingTooLong: " << header + << "; missing_length: " << missing_length; +} + +void FailingHttp2FrameDecoderListener::OnFrameSizeError( + const Http2FrameHeader& header) { + FAIL() << "OnFrameSizeError: " << header; +} + +LoggingHttp2FrameDecoderListener::LoggingHttp2FrameDecoderListener() + : wrapped_(nullptr) {} +LoggingHttp2FrameDecoderListener::LoggingHttp2FrameDecoderListener( + Http2FrameDecoderListener* wrapped) + : wrapped_(wrapped) {} +LoggingHttp2FrameDecoderListener::~LoggingHttp2FrameDecoderListener() = default; + +bool LoggingHttp2FrameDecoderListener::OnFrameHeader( + const Http2FrameHeader& header) { + VLOG(1) << "OnFrameHeader: " << header; + if (wrapped_ != nullptr) { + return wrapped_->OnFrameHeader(header); + } + return true; +} + +void LoggingHttp2FrameDecoderListener::OnDataStart( + const Http2FrameHeader& header) { + VLOG(1) << "OnDataStart: " << header; + if (wrapped_ != nullptr) { + wrapped_->OnDataStart(header); + } +} + +void LoggingHttp2FrameDecoderListener::OnDataPayload(const char* data, + size_t len) { + VLOG(1) << "OnDataPayload: len=" << len; + if (wrapped_ != nullptr) { + wrapped_->OnDataPayload(data, len); + } +} + +void LoggingHttp2FrameDecoderListener::OnDataEnd() { + VLOG(1) << "OnDataEnd"; + if (wrapped_ != nullptr) { + wrapped_->OnDataEnd(); + } +} + +void LoggingHttp2FrameDecoderListener::OnHeadersStart( + const Http2FrameHeader& header) { + VLOG(1) << "OnHeadersStart: " << header; + if (wrapped_ != nullptr) { + wrapped_->OnHeadersStart(header); + } +} + +void LoggingHttp2FrameDecoderListener::OnHeadersPriority( + const Http2PriorityFields& priority) { + VLOG(1) << "OnHeadersPriority: " << priority; + if (wrapped_ != nullptr) { + wrapped_->OnHeadersPriority(priority); + } +} + +void LoggingHttp2FrameDecoderListener::OnHpackFragment(const char* data, + size_t len) { + VLOG(1) << "OnHpackFragment: len=" << len; + if (wrapped_ != nullptr) { + wrapped_->OnHpackFragment(data, len); + } +} + +void LoggingHttp2FrameDecoderListener::OnHeadersEnd() { + VLOG(1) << "OnHeadersEnd"; + if (wrapped_ != nullptr) { + wrapped_->OnHeadersEnd(); + } +} + +void LoggingHttp2FrameDecoderListener::OnPriorityFrame( + const Http2FrameHeader& header, + const Http2PriorityFields& priority) { + VLOG(1) << "OnPriorityFrame: " << header << "; priority: " << priority; + if (wrapped_ != nullptr) { + wrapped_->OnPriorityFrame(header, priority); + } +} + +void LoggingHttp2FrameDecoderListener::OnContinuationStart( + const Http2FrameHeader& header) { + VLOG(1) << "OnContinuationStart: " << header; + if (wrapped_ != nullptr) { + wrapped_->OnContinuationStart(header); + } +} + +void LoggingHttp2FrameDecoderListener::OnContinuationEnd() { + VLOG(1) << "OnContinuationEnd"; + if (wrapped_ != nullptr) { + wrapped_->OnContinuationEnd(); + } +} + +void LoggingHttp2FrameDecoderListener::OnPadLength(size_t trailing_length) { + VLOG(1) << "OnPadLength: trailing_length=" << trailing_length; + if (wrapped_ != nullptr) { + wrapped_->OnPadLength(trailing_length); + } +} + +void LoggingHttp2FrameDecoderListener::OnPadding(const char* padding, + size_t skipped_length) { + VLOG(1) << "OnPadding: skipped_length=" << skipped_length; + if (wrapped_ != nullptr) { + wrapped_->OnPadding(padding, skipped_length); + } +} + +void LoggingHttp2FrameDecoderListener::OnRstStream( + const Http2FrameHeader& header, + Http2ErrorCode error_code) { + VLOG(1) << "OnRstStream: " << header << "; code=" << error_code; + if (wrapped_ != nullptr) { + wrapped_->OnRstStream(header, error_code); + } +} + +void LoggingHttp2FrameDecoderListener::OnSettingsStart( + const Http2FrameHeader& header) { + VLOG(1) << "OnSettingsStart: " << header; + if (wrapped_ != nullptr) { + wrapped_->OnSettingsStart(header); + } +} + +void LoggingHttp2FrameDecoderListener::OnSetting( + const Http2SettingFields& setting_fields) { + VLOG(1) << "OnSetting: " << setting_fields; + if (wrapped_ != nullptr) { + wrapped_->OnSetting(setting_fields); + } +} + +void LoggingHttp2FrameDecoderListener::OnSettingsEnd() { + VLOG(1) << "OnSettingsEnd"; + if (wrapped_ != nullptr) { + wrapped_->OnSettingsEnd(); + } +} + +void LoggingHttp2FrameDecoderListener::OnSettingsAck( + const Http2FrameHeader& header) { + VLOG(1) << "OnSettingsAck: " << header; + if (wrapped_ != nullptr) { + wrapped_->OnSettingsAck(header); + } +} + +void LoggingHttp2FrameDecoderListener::OnPushPromiseStart( + const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) { + VLOG(1) << "OnPushPromiseStart: " << header << "; promise: " << promise + << "; total_padding_length: " << total_padding_length; + if (wrapped_ != nullptr) { + wrapped_->OnPushPromiseStart(header, promise, total_padding_length); + } +} + +void LoggingHttp2FrameDecoderListener::OnPushPromiseEnd() { + VLOG(1) << "OnPushPromiseEnd"; + if (wrapped_ != nullptr) { + wrapped_->OnPushPromiseEnd(); + } +} + +void LoggingHttp2FrameDecoderListener::OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) { + VLOG(1) << "OnPing: " << header << "; ping: " << ping; + if (wrapped_ != nullptr) { + wrapped_->OnPing(header, ping); + } +} + +void LoggingHttp2FrameDecoderListener::OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) { + VLOG(1) << "OnPingAck: " << header << "; ping: " << ping; + if (wrapped_ != nullptr) { + wrapped_->OnPingAck(header, ping); + } +} + +void LoggingHttp2FrameDecoderListener::OnGoAwayStart( + const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) { + VLOG(1) << "OnGoAwayStart: " << header << "; goaway: " << goaway; + if (wrapped_ != nullptr) { + wrapped_->OnGoAwayStart(header, goaway); + } +} + +void LoggingHttp2FrameDecoderListener::OnGoAwayOpaqueData(const char* data, + size_t len) { + VLOG(1) << "OnGoAwayOpaqueData: len=" << len; + if (wrapped_ != nullptr) { + wrapped_->OnGoAwayOpaqueData(data, len); + } +} + +void LoggingHttp2FrameDecoderListener::OnGoAwayEnd() { + VLOG(1) << "OnGoAwayEnd"; + if (wrapped_ != nullptr) { + wrapped_->OnGoAwayEnd(); + } +} + +void LoggingHttp2FrameDecoderListener::OnWindowUpdate( + const Http2FrameHeader& header, + uint32_t increment) { + VLOG(1) << "OnWindowUpdate: " << header << "; increment=" << increment; + if (wrapped_ != nullptr) { + wrapped_->OnWindowUpdate(header, increment); + } +} + +void LoggingHttp2FrameDecoderListener::OnAltSvcStart( + const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) { + VLOG(1) << "OnAltSvcStart: " << header << "; origin_length: " << origin_length + << "; value_length: " << value_length; + if (wrapped_ != nullptr) { + wrapped_->OnAltSvcStart(header, origin_length, value_length); + } +} + +void LoggingHttp2FrameDecoderListener::OnAltSvcOriginData(const char* data, + size_t len) { + VLOG(1) << "OnAltSvcOriginData: len=" << len; + if (wrapped_ != nullptr) { + wrapped_->OnAltSvcOriginData(data, len); + } +} + +void LoggingHttp2FrameDecoderListener::OnAltSvcValueData(const char* data, + size_t len) { + VLOG(1) << "OnAltSvcValueData: len=" << len; + if (wrapped_ != nullptr) { + wrapped_->OnAltSvcValueData(data, len); + } +} + +void LoggingHttp2FrameDecoderListener::OnAltSvcEnd() { + VLOG(1) << "OnAltSvcEnd"; + if (wrapped_ != nullptr) { + wrapped_->OnAltSvcEnd(); + } +} + +void LoggingHttp2FrameDecoderListener::OnUnknownStart( + const Http2FrameHeader& header) { + VLOG(1) << "OnUnknownStart: " << header; + if (wrapped_ != nullptr) { + wrapped_->OnUnknownStart(header); + } +} + +void LoggingHttp2FrameDecoderListener::OnUnknownPayload(const char* data, + size_t len) { + VLOG(1) << "OnUnknownPayload: len=" << len; + if (wrapped_ != nullptr) { + wrapped_->OnUnknownPayload(data, len); + } +} + +void LoggingHttp2FrameDecoderListener::OnUnknownEnd() { + VLOG(1) << "OnUnknownEnd"; + if (wrapped_ != nullptr) { + wrapped_->OnUnknownEnd(); + } +} + +void LoggingHttp2FrameDecoderListener::OnPaddingTooLong( + const Http2FrameHeader& header, + size_t missing_length) { + VLOG(1) << "OnPaddingTooLong: " << header + << "; missing_length: " << missing_length; + if (wrapped_ != nullptr) { + wrapped_->OnPaddingTooLong(header, missing_length); + } +} + +void LoggingHttp2FrameDecoderListener::OnFrameSizeError( + const Http2FrameHeader& header) { + VLOG(1) << "OnFrameSizeError: " << header; + if (wrapped_ != nullptr) { + wrapped_->OnFrameSizeError(header); + } +} + +} // namespace http2
diff --git a/http2/decoder/http2_frame_decoder_listener_test_util.h b/http2/decoder/http2_frame_decoder_listener_test_util.h new file mode 100644 index 0000000..d6e84ef --- /dev/null +++ b/http2/decoder/http2_frame_decoder_listener_test_util.h
@@ -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. + +#ifndef QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_LISTENER_TEST_UTIL_H_ +#define QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_LISTENER_TEST_UTIL_H_ + +#include <stddef.h> + +#include <cstdint> + +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" + +namespace http2 { + +// Fail if any of the methods are called. Allows a test to override only the +// expected calls. +class FailingHttp2FrameDecoderListener : public Http2FrameDecoderListener { + public: + FailingHttp2FrameDecoderListener(); + ~FailingHttp2FrameDecoderListener() override; + + // TODO(jamessynge): Remove OnFrameHeader once done with supporting + // SpdyFramer's exact states. + bool OnFrameHeader(const Http2FrameHeader& header) override; + void OnDataStart(const Http2FrameHeader& header) override; + void OnDataPayload(const char* data, size_t len) override; + void OnDataEnd() override; + void OnHeadersStart(const Http2FrameHeader& header) override; + void OnHeadersPriority(const Http2PriorityFields& priority) override; + void OnHpackFragment(const char* data, size_t len) override; + void OnHeadersEnd() override; + void OnPriorityFrame(const Http2FrameHeader& header, + const Http2PriorityFields& priority) override; + void OnContinuationStart(const Http2FrameHeader& header) override; + void OnContinuationEnd() override; + void OnPadLength(size_t trailing_length) override; + void OnPadding(const char* padding, size_t skipped_length) override; + void OnRstStream(const Http2FrameHeader& header, + Http2ErrorCode error_code) override; + void OnSettingsStart(const Http2FrameHeader& header) override; + void OnSetting(const Http2SettingFields& setting_fields) override; + void OnSettingsEnd() override; + void OnSettingsAck(const Http2FrameHeader& header) override; + void OnPushPromiseStart(const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) override; + void OnPushPromiseEnd() override; + void OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) override; + void OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) override; + void OnGoAwayStart(const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) override; + void OnGoAwayOpaqueData(const char* data, size_t len) override; + void OnGoAwayEnd() override; + void OnWindowUpdate(const Http2FrameHeader& header, + uint32_t increment) override; + void OnAltSvcStart(const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) override; + void OnAltSvcOriginData(const char* data, size_t len) override; + void OnAltSvcValueData(const char* data, size_t len) override; + void OnAltSvcEnd() override; + void OnUnknownStart(const Http2FrameHeader& header) override; + void OnUnknownPayload(const char* data, size_t len) override; + void OnUnknownEnd() override; + void OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) override; + void OnFrameSizeError(const Http2FrameHeader& header) override; + + private: + void EnsureNotAbstract() { FailingHttp2FrameDecoderListener instance; } +}; + +// VLOG's all the calls it receives, and forwards those calls to an optional +// listener. +class LoggingHttp2FrameDecoderListener : public Http2FrameDecoderListener { + public: + LoggingHttp2FrameDecoderListener(); + explicit LoggingHttp2FrameDecoderListener(Http2FrameDecoderListener* wrapped); + ~LoggingHttp2FrameDecoderListener() override; + + // TODO(jamessynge): Remove OnFrameHeader once done with supporting + // SpdyFramer's exact states. + bool OnFrameHeader(const Http2FrameHeader& header) override; + void OnDataStart(const Http2FrameHeader& header) override; + void OnDataPayload(const char* data, size_t len) override; + void OnDataEnd() override; + void OnHeadersStart(const Http2FrameHeader& header) override; + void OnHeadersPriority(const Http2PriorityFields& priority) override; + void OnHpackFragment(const char* data, size_t len) override; + void OnHeadersEnd() override; + void OnPriorityFrame(const Http2FrameHeader& header, + const Http2PriorityFields& priority) override; + void OnContinuationStart(const Http2FrameHeader& header) override; + void OnContinuationEnd() override; + void OnPadLength(size_t trailing_length) override; + void OnPadding(const char* padding, size_t skipped_length) override; + void OnRstStream(const Http2FrameHeader& header, + Http2ErrorCode error_code) override; + void OnSettingsStart(const Http2FrameHeader& header) override; + void OnSetting(const Http2SettingFields& setting_fields) override; + void OnSettingsEnd() override; + void OnSettingsAck(const Http2FrameHeader& header) override; + void OnPushPromiseStart(const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) override; + void OnPushPromiseEnd() override; + void OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) override; + void OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) override; + void OnGoAwayStart(const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) override; + void OnGoAwayOpaqueData(const char* data, size_t len) override; + void OnGoAwayEnd() override; + void OnWindowUpdate(const Http2FrameHeader& header, + uint32_t increment) override; + void OnAltSvcStart(const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) override; + void OnAltSvcOriginData(const char* data, size_t len) override; + void OnAltSvcValueData(const char* data, size_t len) override; + void OnAltSvcEnd() override; + void OnUnknownStart(const Http2FrameHeader& header) override; + void OnUnknownPayload(const char* data, size_t len) override; + void OnUnknownEnd() override; + void OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) override; + void OnFrameSizeError(const Http2FrameHeader& header) override; + + private: + void EnsureNotAbstract() { LoggingHttp2FrameDecoderListener instance; } + + Http2FrameDecoderListener* wrapped_; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_LISTENER_TEST_UTIL_H_
diff --git a/http2/decoder/http2_frame_decoder_test.cc b/http2/decoder/http2_frame_decoder_test.cc new file mode 100644 index 0000000..172bd62 --- /dev/null +++ b/http2/decoder/http2_frame_decoder_test.cc
@@ -0,0 +1,929 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder.h" + +// Tests of Http2FrameDecoder. + +#include <vector> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_reconstruct_object.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; + +namespace http2 { +namespace test { +class Http2FrameDecoderPeer { + public: + static size_t remaining_total_payload(Http2FrameDecoder* decoder) { + return decoder->frame_decoder_state_.remaining_total_payload(); + } +}; + +namespace { + +class Http2FrameDecoderTest : public RandomDecoderTest { + protected: + void SetUp() override { + // On any one run of this suite, we'll always choose the same value for + // use_default_reconstruct_ because the random seed is the same for each + // test case, but across runs the random seed changes. + use_default_reconstruct_ = Random().OneIn(2); + } + + DecodeStatus StartDecoding(DecodeBuffer* db) override { + DVLOG(2) << "StartDecoding, db->Remaining=" << db->Remaining(); + collector_.Reset(); + PrepareDecoder(); + + DecodeStatus status = decoder_.DecodeFrame(db); + if (status != DecodeStatus::kDecodeInProgress) { + // Keep track of this so that a concrete test can verify that both fast + // and slow decoding paths have been tested. + ++fast_decode_count_; + if (status == DecodeStatus::kDecodeError) { + ConfirmDiscardsRemainingPayload(); + } + } + return status; + } + + DecodeStatus ResumeDecoding(DecodeBuffer* db) override { + DVLOG(2) << "ResumeDecoding, db->Remaining=" << db->Remaining(); + DecodeStatus status = decoder_.DecodeFrame(db); + if (status != DecodeStatus::kDecodeInProgress) { + // Keep track of this so that a concrete test can verify that both fast + // and slow decoding paths have been tested. + ++slow_decode_count_; + if (status == DecodeStatus::kDecodeError) { + ConfirmDiscardsRemainingPayload(); + } + } + return status; + } + + // When an error is returned, the decoder is in state kDiscardPayload, and + // stays there until the remaining bytes of the frame's payload have been + // skipped over. There are no callbacks for this situation. + void ConfirmDiscardsRemainingPayload() { + ASSERT_TRUE(decoder_.IsDiscardingPayload()); + size_t remaining = + Http2FrameDecoderPeer::remaining_total_payload(&decoder_); + // The decoder will discard the remaining bytes, but not go beyond that, + // which these conditions verify. + size_t extra = 10; + Http2String junk(remaining + extra, '0'); + DecodeBuffer tmp(junk); + EXPECT_EQ(DecodeStatus::kDecodeDone, decoder_.DecodeFrame(&tmp)); + EXPECT_EQ(remaining, tmp.Offset()); + EXPECT_EQ(extra, tmp.Remaining()); + EXPECT_FALSE(decoder_.IsDiscardingPayload()); + } + + void PrepareDecoder() { + // Save and restore the maximum_payload_size when reconstructing + // the decoder. + size_t maximum_payload_size = decoder_.maximum_payload_size(); + + // Alternate which constructor is used. + if (use_default_reconstruct_) { + Http2DefaultReconstructObject(&decoder_, RandomPtr()); + decoder_.set_listener(&collector_); + } else { + Http2ReconstructObject(&decoder_, RandomPtr(), &collector_); + } + decoder_.set_maximum_payload_size(maximum_payload_size); + + use_default_reconstruct_ = !use_default_reconstruct_; + } + + void ResetDecodeSpeedCounters() { + fast_decode_count_ = 0; + slow_decode_count_ = 0; + } + + AssertionResult VerifyCollected(const FrameParts& expected) { + VERIFY_FALSE(collector_.IsInProgress()); + VERIFY_EQ(1u, collector_.size()); + VERIFY_AND_RETURN_SUCCESS(expected.VerifyEquals(*collector_.frame(0))); + } + + AssertionResult DecodePayloadAndValidateSeveralWays(Http2StringPiece payload, + Validator validator) { + DecodeBuffer db(payload); + bool start_decoding_requires_non_empty = false; + return DecodeAndValidateSeveralWays(&db, start_decoding_requires_non_empty, + validator); + } + + // Decode one frame's payload and confirm that the listener recorded the + // expected FrameParts instance, and only one FrameParts instance. The + // payload will be decoded several times with different partitionings + // of the payload, and after each the validator will be called. + AssertionResult DecodePayloadAndValidateSeveralWays( + Http2StringPiece payload, + const FrameParts& expected) { + auto validator = [&expected, this](const DecodeBuffer& input, + DecodeStatus status) -> AssertionResult { + VERIFY_EQ(status, DecodeStatus::kDecodeDone); + VERIFY_AND_RETURN_SUCCESS(VerifyCollected(expected)); + }; + ResetDecodeSpeedCounters(); + VERIFY_SUCCESS(DecodePayloadAndValidateSeveralWays( + payload, ValidateDoneAndEmpty(validator))); + VERIFY_GT(fast_decode_count_, 0u); + VERIFY_GT(slow_decode_count_, 0u); + + // Repeat with more input; it should stop without reading that input. + Http2String next_frame = Random().RandString(10); + Http2String input(payload.data(), payload.size()); + input += next_frame; + + ResetDecodeSpeedCounters(); + VERIFY_SUCCESS(DecodePayloadAndValidateSeveralWays( + payload, ValidateDoneAndOffset(payload.size(), validator))); + VERIFY_GT(fast_decode_count_, 0u); + VERIFY_GT(slow_decode_count_, 0u); + + return AssertionSuccess(); + } + + template <size_t N> + AssertionResult DecodePayloadAndValidateSeveralWays( + const char (&buf)[N], + const FrameParts& expected) { + return DecodePayloadAndValidateSeveralWays(Http2StringPiece(buf, N), + expected); + } + + template <size_t N> + AssertionResult DecodePayloadAndValidateSeveralWays( + const char (&buf)[N], + const Http2FrameHeader& header) { + return DecodePayloadAndValidateSeveralWays(Http2StringPiece(buf, N), + FrameParts(header)); + } + + template <size_t N> + AssertionResult DecodePayloadExpectingError(const char (&buf)[N], + const FrameParts& expected) { + auto validator = [&expected, this](const DecodeBuffer& input, + DecodeStatus status) -> AssertionResult { + VERIFY_EQ(status, DecodeStatus::kDecodeError); + VERIFY_AND_RETURN_SUCCESS(VerifyCollected(expected)); + }; + ResetDecodeSpeedCounters(); + EXPECT_TRUE( + DecodePayloadAndValidateSeveralWays(ToStringPiece(buf), validator)); + EXPECT_GT(fast_decode_count_, 0u); + EXPECT_GT(slow_decode_count_, 0u); + return AssertionSuccess(); + } + + template <size_t N> + AssertionResult DecodePayloadExpectingFrameSizeError(const char (&buf)[N], + FrameParts expected) { + expected.SetHasFrameSizeError(true); + VERIFY_AND_RETURN_SUCCESS(DecodePayloadExpectingError(buf, expected)); + } + + template <size_t N> + AssertionResult DecodePayloadExpectingFrameSizeError( + const char (&buf)[N], + const Http2FrameHeader& header) { + return DecodePayloadExpectingFrameSizeError(buf, FrameParts(header)); + } + + // Count of payloads that are fully decoded by StartDecodingPayload or for + // which an error was detected by StartDecodingPayload. + size_t fast_decode_count_ = 0; + + // Count of payloads that required calling ResumeDecodingPayload in order to + // decode completely, or for which an error was detected by + // ResumeDecodingPayload. + size_t slow_decode_count_ = 0; + + FramePartsCollectorListener collector_; + Http2FrameDecoder decoder_; + bool use_default_reconstruct_; +}; + +//////////////////////////////////////////////////////////////////////////////// +// Tests that pass the minimum allowed size for the frame type, which is often +// empty. The tests are in order by frame type value (i.e. 0 for DATA frames). + +TEST_F(Http2FrameDecoderTest, DataEmpty) { + const char kFrameData[] = { + '\x00', '\x00', '\x00', // Payload length: 0 + '\x00', // DATA + '\x00', // Flags: none + '\x00', '\x00', '\x00', + '\x00', // Stream ID: 0 (invalid but unchecked here) + }; + Http2FrameHeader header(0, Http2FrameType::DATA, 0, 0); + FrameParts expected(header, ""); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, HeadersEmpty) { + const char kFrameData[] = { + '\x00', '\x00', '\x00', // Payload length: 0 + '\x01', // HEADERS + '\x00', // Flags: none + '\x00', '\x00', '\x00', '\x01', // Stream ID: 0 (REQUIRES ID) + }; + Http2FrameHeader header(0, Http2FrameType::HEADERS, 0, 1); + FrameParts expected(header, ""); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, Priority) { + const char kFrameData[] = { + '\x00', '\x00', '\x05', // Length: 5 + '\x02', // Type: PRIORITY + '\x00', // Flags: none + '\x00', '\x00', '\x00', '\x02', // Stream: 2 + '\x80', '\x00', '\x00', '\x01', // Parent: 1 (Exclusive) + '\x10', // Weight: 17 + }; + Http2FrameHeader header(5, Http2FrameType::PRIORITY, 0, 2); + FrameParts expected(header); + expected.SetOptPriority(Http2PriorityFields(1, 17, true)); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, RstStream) { + const char kFrameData[] = { + '\x00', '\x00', '\x04', // Length: 4 + '\x03', // Type: RST_STREAM + '\x00', // Flags: none + '\x00', '\x00', '\x00', '\x01', // Stream: 1 + '\x00', '\x00', '\x00', '\x01', // Error: PROTOCOL_ERROR + }; + Http2FrameHeader header(4, Http2FrameType::RST_STREAM, 0, 1); + FrameParts expected(header); + expected.SetOptRstStreamErrorCode(Http2ErrorCode::PROTOCOL_ERROR); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, SettingsEmpty) { + const char kFrameData[] = { + '\x00', '\x00', '\x00', // Length: 0 + '\x04', // Type: SETTINGS + '\x00', // Flags: none + '\x00', '\x00', '\x00', '\x01', // Stream: 1 (invalid but unchecked here) + }; + Http2FrameHeader header(0, Http2FrameType::SETTINGS, 0, 1); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, SettingsAck) { + const char kFrameData[] = { + '\x00', '\x00', '\x00', // Length: 6 + '\x04', // Type: SETTINGS + '\x01', // Flags: ACK + '\x00', '\x00', '\x00', '\x00', // Stream: 0 + }; + Http2FrameHeader header(0, Http2FrameType::SETTINGS, Http2FrameFlag::ACK, 0); + FrameParts expected(header); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, PushPromiseMinimal) { + const char kFrameData[] = { + '\x00', '\x00', '\x04', // Payload length: 4 + '\x05', // PUSH_PROMISE + '\x04', // Flags: END_HEADERS + '\x00', '\x00', '\x00', + '\x02', // Stream: 2 (invalid but unchecked here) + '\x00', '\x00', '\x00', + '\x01', // Promised: 1 (invalid but unchecked here) + }; + Http2FrameHeader header(4, Http2FrameType::PUSH_PROMISE, + Http2FrameFlag::END_HEADERS, 2); + FrameParts expected(header, ""); + expected.SetOptPushPromise(Http2PushPromiseFields{1}); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, Ping) { + const char kFrameData[] = { + '\x00', '\x00', '\x08', // Length: 8 + '\x06', // Type: PING + '\xfe', // Flags: no valid flags + '\x00', '\x00', '\x00', '\x00', // Stream: 0 + 's', 'o', 'm', 'e', // "some" + 'd', 'a', 't', 'a', // "data" + }; + Http2FrameHeader header(8, Http2FrameType::PING, 0, 0); + FrameParts expected(header); + expected.SetOptPing( + Http2PingFields{{'s', 'o', 'm', 'e', 'd', 'a', 't', 'a'}}); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, PingAck) { + const char kFrameData[] = { + '\x00', '\x00', '\x08', // Length: 8 + '\x06', // Type: PING + '\xff', // Flags: ACK (plus all invalid flags) + '\x00', '\x00', '\x00', '\x00', // Stream: 0 + 's', 'o', 'm', 'e', // "some" + 'd', 'a', 't', 'a', // "data" + }; + Http2FrameHeader header(8, Http2FrameType::PING, Http2FrameFlag::ACK, 0); + FrameParts expected(header); + expected.SetOptPing( + Http2PingFields{{'s', 'o', 'm', 'e', 'd', 'a', 't', 'a'}}); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, GoAwayMinimal) { + const char kFrameData[] = { + '\x00', '\x00', '\x08', // Length: 8 (no opaque data) + '\x07', // Type: GOAWAY + '\xff', // Flags: 0xff (no valid flags) + '\x00', '\x00', '\x00', '\x01', // Stream: 1 (invalid but unchecked here) + '\x80', '\x00', '\x00', '\xff', // Last: 255 (plus R bit) + '\x00', '\x00', '\x00', '\x09', // Error: COMPRESSION_ERROR + }; + Http2FrameHeader header(8, Http2FrameType::GOAWAY, 0, 1); + FrameParts expected(header); + expected.SetOptGoaway( + Http2GoAwayFields(255, Http2ErrorCode::COMPRESSION_ERROR)); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, WindowUpdate) { + const char kFrameData[] = { + '\x00', '\x00', '\x04', // Length: 4 + '\x08', // Type: WINDOW_UPDATE + '\x0f', // Flags: 0xff (no valid flags) + '\x00', '\x00', '\x00', '\x01', // Stream: 1 + '\x80', '\x00', '\x04', '\x00', // Incr: 1024 (plus R bit) + }; + Http2FrameHeader header(4, Http2FrameType::WINDOW_UPDATE, 0, 1); + FrameParts expected(header); + expected.SetOptWindowUpdateIncrement(1024); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, ContinuationEmpty) { + const char kFrameData[] = { + '\x00', '\x00', '\x00', // Payload length: 0 + '\x09', // CONTINUATION + '\x00', // Flags: none + '\x00', '\x00', '\x00', + '\x00', // Stream ID: 0 (invalid but unchecked here) + }; + Http2FrameHeader header(0, Http2FrameType::CONTINUATION, 0, 0); + FrameParts expected(header); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, AltSvcMinimal) { + const char kFrameData[] = { + '\x00', '\x00', '\x02', // Payload length: 2 + '\x0a', // ALTSVC + '\xff', // Flags: none (plus 0xff) + '\x00', '\x00', '\x00', + '\x00', // Stream ID: 0 (invalid but unchecked here) + '\x00', '\x00', // Origin Length: 0 + }; + Http2FrameHeader header(2, Http2FrameType::ALTSVC, 0, 0); + FrameParts expected(header); + expected.SetOptAltsvcOriginLength(0); + expected.SetOptAltsvcValueLength(0); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, UnknownEmpty) { + const char kFrameData[] = { + '\x00', '\x00', '\x00', // Payload length: 0 + '\x20', // 32 (unknown) + '\xff', // Flags: all + '\x00', '\x00', '\x00', '\x00', // Stream ID: 0 + }; + Http2FrameHeader header(0, static_cast<Http2FrameType>(32), 0xff, 0); + FrameParts expected(header); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Tests of longer payloads, for those frame types that allow longer payloads. + +TEST_F(Http2FrameDecoderTest, DataPayload) { + const char kFrameData[] = { + '\x00', '\x00', '\x03', // Payload length: 7 + '\x00', // DATA + '\x80', // Flags: 0x80 + '\x00', '\x00', '\x02', '\x02', // Stream ID: 514 + 'a', 'b', 'c', // Data + }; + Http2FrameHeader header(3, Http2FrameType::DATA, 0, 514); + FrameParts expected(header, "abc"); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, HeadersPayload) { + const char kFrameData[] = { + '\x00', '\x00', '\x03', // Payload length: 3 + '\x01', // HEADERS + '\x05', // Flags: END_STREAM | END_HEADERS + '\x00', '\x00', '\x00', '\x02', // Stream ID: 0 (REQUIRES ID) + 'a', 'b', 'c', // HPACK fragment (doesn't have to be valid) + }; + Http2FrameHeader header( + 3, Http2FrameType::HEADERS, + Http2FrameFlag::END_STREAM | Http2FrameFlag::END_HEADERS, 2); + FrameParts expected(header, "abc"); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, HeadersPriority) { + const char kFrameData[] = { + '\x00', '\x00', '\x05', // Payload length: 5 + '\x01', // HEADERS + '\x20', // Flags: PRIORITY + '\x00', '\x00', '\x00', '\x02', // Stream ID: 0 (REQUIRES ID) + '\x00', '\x00', '\x00', '\x01', // Parent: 1 (Not Exclusive) + '\xff', // Weight: 256 + }; + Http2FrameHeader header(5, Http2FrameType::HEADERS, Http2FrameFlag::PRIORITY, + 2); + FrameParts expected(header); + expected.SetOptPriority(Http2PriorityFields(1, 256, false)); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, Settings) { + const char kFrameData[] = { + '\x00', '\x00', '\x0c', // Length: 12 + '\x04', // Type: SETTINGS + '\x00', // Flags: none + '\x00', '\x00', '\x00', '\x00', // Stream: 0 + '\x00', '\x04', // Param: INITIAL_WINDOW_SIZE + '\x0a', '\x0b', '\x0c', '\x0d', // Value: 168496141 + '\x00', '\x02', // Param: ENABLE_PUSH + '\x00', '\x00', '\x00', '\x03', // Value: 3 (invalid but unchecked here) + }; + Http2FrameHeader header(12, Http2FrameType::SETTINGS, 0, 0); + FrameParts expected(header); + expected.AppendSetting(Http2SettingFields( + Http2SettingsParameter::INITIAL_WINDOW_SIZE, 168496141)); + expected.AppendSetting( + Http2SettingFields(Http2SettingsParameter::ENABLE_PUSH, 3)); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, PushPromisePayload) { + const char kFrameData[] = { + '\x00', '\x00', 7, // Payload length: 7 + '\x05', // PUSH_PROMISE + '\x04', // Flags: END_HEADERS + '\x00', '\x00', '\x00', '\xff', // Stream ID: 255 + '\x00', '\x00', '\x01', '\x00', // Promised: 256 + 'a', 'b', 'c', // HPACK fragment (doesn't have to be valid) + }; + Http2FrameHeader header(7, Http2FrameType::PUSH_PROMISE, + Http2FrameFlag::END_HEADERS, 255); + FrameParts expected(header, "abc"); + expected.SetOptPushPromise(Http2PushPromiseFields{256}); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, GoAwayOpaqueData) { + const char kFrameData[] = { + '\x00', '\x00', '\x0e', // Length: 14 + '\x07', // Type: GOAWAY + '\xff', // Flags: 0xff (no valid flags) + '\x80', '\x00', '\x00', '\x00', // Stream: 0 (plus R bit) + '\x00', '\x00', '\x01', '\x00', // Last: 256 + '\x00', '\x00', '\x00', '\x03', // Error: FLOW_CONTROL_ERROR + 'o', 'p', 'a', 'q', 'u', 'e', + }; + Http2FrameHeader header(14, Http2FrameType::GOAWAY, 0, 0); + FrameParts expected(header, "opaque"); + expected.SetOptGoaway( + Http2GoAwayFields(256, Http2ErrorCode::FLOW_CONTROL_ERROR)); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, ContinuationPayload) { + const char kFrameData[] = { + '\x00', '\x00', '\x03', // Payload length: 3 + '\x09', // CONTINUATION + '\xff', // Flags: END_HEADERS | 0xfb + '\x00', '\x00', '\x00', '\x02', // Stream ID: 2 + 'a', 'b', 'c', // Data + }; + Http2FrameHeader header(3, Http2FrameType::CONTINUATION, + Http2FrameFlag::END_HEADERS, 2); + FrameParts expected(header, "abc"); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, AltSvcPayload) { + const char kFrameData[] = { + '\x00', '\x00', '\x08', // Payload length: 3 + '\x0a', // ALTSVC + '\x00', // Flags: none + '\x00', '\x00', '\x00', '\x02', // Stream ID: 2 + '\x00', '\x03', // Origin Length: 0 + 'a', 'b', 'c', // Origin + 'd', 'e', 'f', // Value + }; + Http2FrameHeader header(8, Http2FrameType::ALTSVC, 0, 2); + FrameParts expected(header); + expected.SetAltSvcExpected("abc", "def"); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, UnknownPayload) { + const char kFrameData[] = { + '\x00', '\x00', '\x03', // Payload length: 3 + '\x30', // 48 (unknown) + '\x00', // Flags: none + '\x00', '\x00', '\x00', '\x02', // Stream ID: 2 + 'a', 'b', 'c', // Payload + }; + Http2FrameHeader header(3, static_cast<Http2FrameType>(48), 0, 2); + FrameParts expected(header, "abc"); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Tests of padded payloads, for those frame types that allow padding. + +TEST_F(Http2FrameDecoderTest, DataPayloadAndPadding) { + const char kFrameData[] = { + '\x00', '\x00', '\x07', // Payload length: 7 + '\x00', // DATA + '\x09', // Flags: END_STREAM | PADDED + '\x00', '\x00', '\x00', '\x02', // Stream ID: 0 (REQUIRES ID) + '\x03', // Pad Len + 'a', 'b', 'c', // Data + '\x00', '\x00', '\x00', // Padding + }; + Http2FrameHeader header(7, Http2FrameType::DATA, + Http2FrameFlag::END_STREAM | Http2FrameFlag::PADDED, + 2); + size_t total_pad_length = 4; // Including the Pad Length field. + FrameParts expected(header, "abc", total_pad_length); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, HeadersPayloadAndPadding) { + const char kFrameData[] = { + '\x00', '\x00', '\x07', // Payload length: 7 + '\x01', // HEADERS + '\x08', // Flags: PADDED + '\x00', '\x00', '\x00', '\x02', // Stream ID: 0 (REQUIRES ID) + '\x03', // Pad Len + 'a', 'b', 'c', // HPACK fragment (doesn't have to be valid) + '\x00', '\x00', '\x00', // Padding + }; + Http2FrameHeader header(7, Http2FrameType::HEADERS, Http2FrameFlag::PADDED, + 2); + size_t total_pad_length = 4; // Including the Pad Length field. + FrameParts expected(header, "abc", total_pad_length); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, HeadersPayloadPriorityAndPadding) { + const char kFrameData[] = { + '\x00', '\x00', '\x0c', // Payload length: 12 + '\x01', // HEADERS + '\xff', // Flags: all, including undefined + '\x00', '\x00', '\x00', '\x02', // Stream ID: 0 (REQUIRES ID) + '\x03', // Pad Len + '\x80', '\x00', '\x00', '\x01', // Parent: 1 (Exclusive) + '\x10', // Weight: 17 + 'a', 'b', 'c', // HPACK fragment (doesn't have to be valid) + '\x00', '\x00', '\x00', // Padding + }; + Http2FrameHeader header(12, Http2FrameType::HEADERS, + Http2FrameFlag::END_STREAM | + Http2FrameFlag::END_HEADERS | + Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY, + 2); + size_t total_pad_length = 4; // Including the Pad Length field. + FrameParts expected(header, "abc", total_pad_length); + expected.SetOptPriority(Http2PriorityFields(1, 17, true)); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, PushPromisePayloadAndPadding) { + const char kFrameData[] = { + '\x00', '\x00', 11, // Payload length: 11 + '\x05', // PUSH_PROMISE + '\xff', // Flags: END_HEADERS | PADDED | 0xf3 + '\x00', '\x00', '\x00', '\x01', // Stream ID: 1 + '\x03', // Pad Len + '\x00', '\x00', '\x00', '\x02', // Promised: 2 + 'a', 'b', 'c', // HPACK fragment (doesn't have to be valid) + '\x00', '\x00', '\x00', // Padding + }; + Http2FrameHeader header(11, Http2FrameType::PUSH_PROMISE, + Http2FrameFlag::END_HEADERS | Http2FrameFlag::PADDED, + 1); + size_t total_pad_length = 4; // Including the Pad Length field. + FrameParts expected(header, "abc", total_pad_length); + expected.SetOptPushPromise(Http2PushPromiseFields{2}); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Payload too short errors. + +TEST_F(Http2FrameDecoderTest, DataMissingPadLengthField) { + const char kFrameData[] = { + '\x00', '\x00', '\x00', // Payload length: 0 + '\x00', // DATA + '\x08', // Flags: PADDED + '\x00', '\x00', '\x00', '\x01', // Stream ID: 1 + }; + Http2FrameHeader header(0, Http2FrameType::DATA, Http2FrameFlag::PADDED, 1); + FrameParts expected(header); + expected.SetOptMissingLength(1); + EXPECT_TRUE(DecodePayloadExpectingError(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, HeaderPaddingTooLong) { + const char kFrameData[] = { + '\x00', '\x00', '\x02', // Payload length: 0 + '\x01', // HEADERS + '\x08', // Flags: PADDED + '\x00', '\x01', '\x00', '\x00', // Stream ID: 65536 + '\xff', // Pad Len: 255 + '\x00', // Only one byte of padding + }; + Http2FrameHeader header(2, Http2FrameType::HEADERS, Http2FrameFlag::PADDED, + 65536); + FrameParts expected(header); + expected.SetOptMissingLength(254); + EXPECT_TRUE(DecodePayloadExpectingError(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, HeaderMissingPriority) { + const char kFrameData[] = { + '\x00', '\x00', '\x04', // Payload length: 0 + '\x01', // HEADERS + '\x20', // Flags: PRIORITY + '\x00', '\x01', '\x00', '\x00', // Stream ID: 65536 + '\x00', '\x00', '\x00', '\x00', // Priority (truncated) + }; + Http2FrameHeader header(4, Http2FrameType::HEADERS, Http2FrameFlag::PRIORITY, + 65536); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, PriorityTooShort) { + const char kFrameData[] = { + '\x00', '\x00', '\x04', // Length: 5 + '\x02', // Type: PRIORITY + '\x00', // Flags: none + '\x00', '\x00', '\x00', '\x02', // Stream: 2 + '\x80', '\x00', '\x00', '\x01', // Parent: 1 (Exclusive) + }; + Http2FrameHeader header(4, Http2FrameType::PRIORITY, 0, 2); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, RstStreamTooShort) { + const char kFrameData[] = { + '\x00', '\x00', '\x03', // Length: 4 + '\x03', // Type: RST_STREAM + '\x00', // Flags: none + '\x00', '\x00', '\x00', '\x01', // Stream: 1 + '\x00', '\x00', '\x00', // Truncated + }; + Http2FrameHeader header(3, Http2FrameType::RST_STREAM, 0, 1); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +// SETTINGS frames must a multiple of 6 bytes long, so an 9 byte payload is +// invalid. +TEST_F(Http2FrameDecoderTest, SettingsWrongSize) { + const char kFrameData[] = { + '\x00', '\x00', '\x09', // Length: 2 + '\x04', // Type: SETTINGS + '\x00', // Flags: none + '\x00', '\x00', '\x00', '\x00', // Stream: 0 + '\x00', '\x02', // Param: ENABLE_PUSH + '\x00', '\x00', '\x00', '\x03', // Value: 1 + '\x00', '\x04', // Param: INITIAL_WINDOW_SIZE + '\x00', // Value: Truncated + }; + Http2FrameHeader header(9, Http2FrameType::SETTINGS, 0, 0); + FrameParts expected(header); + expected.AppendSetting( + Http2SettingFields(Http2SettingsParameter::ENABLE_PUSH, 3)); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, PushPromiseTooShort) { + const char kFrameData[] = { + '\x00', '\x00', 3, // Payload length: 3 + '\x05', // PUSH_PROMISE + '\x00', // Flags: none + '\x00', '\x00', '\x00', '\x01', // Stream ID: 1 + '\x00', '\x00', '\x00', // Truncated promise id + }; + Http2FrameHeader header(3, Http2FrameType::PUSH_PROMISE, 0, 1); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, PushPromisePaddedTruncatedPromise) { + const char kFrameData[] = { + '\x00', '\x00', 4, // Payload length: 4 + '\x05', // PUSH_PROMISE + '\x08', // Flags: PADDED + '\x00', '\x00', '\x00', '\x01', // Stream ID: 1 + '\x00', // Pad Len + '\x00', '\x00', '\x00', // Truncated promise id + }; + Http2FrameHeader header(4, Http2FrameType::PUSH_PROMISE, + Http2FrameFlag::PADDED, 1); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, PingTooShort) { + const char kFrameData[] = { + '\x00', '\x00', '\x07', // Length: 8 + '\x06', // Type: PING + '\xfe', // Flags: no valid flags + '\x00', '\x00', '\x00', '\x00', // Stream: 0 + 's', 'o', 'm', 'e', // "some" + 'd', 'a', 't', // Too little + }; + Http2FrameHeader header(7, Http2FrameType::PING, 0, 0); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, GoAwayTooShort) { + const char kFrameData[] = { + '\x00', '\x00', '\x00', // Length: 0 + '\x07', // Type: GOAWAY + '\xff', // Flags: 0xff (no valid flags) + '\x00', '\x00', '\x00', '\x00', // Stream: 0 + }; + Http2FrameHeader header(0, Http2FrameType::GOAWAY, 0, 0); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, WindowUpdateTooShort) { + const char kFrameData[] = { + '\x00', '\x00', '\x03', // Length: 3 + '\x08', // Type: WINDOW_UPDATE + '\x0f', // Flags: 0xff (no valid flags) + '\x00', '\x00', '\x00', '\x01', // Stream: 1 + '\x80', '\x00', '\x04', // Truncated + }; + Http2FrameHeader header(3, Http2FrameType::WINDOW_UPDATE, 0, 1); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, AltSvcTruncatedOriginLength) { + const char kFrameData[] = { + '\x00', '\x00', '\x01', // Payload length: 3 + '\x0a', // ALTSVC + '\x00', // Flags: none + '\x00', '\x00', '\x00', '\x02', // Stream ID: 2 + '\x00', // Origin Length: truncated + }; + Http2FrameHeader header(1, Http2FrameType::ALTSVC, 0, 2); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, AltSvcTruncatedOrigin) { + const char kFrameData[] = { + '\x00', '\x00', '\x05', // Payload length: 3 + '\x0a', // ALTSVC + '\x00', // Flags: none + '\x00', '\x00', '\x00', '\x02', // Stream ID: 2 + '\x00', '\x04', // Origin Length: 4 (too long) + 'a', 'b', 'c', // Origin + }; + Http2FrameHeader header(5, Http2FrameType::ALTSVC, 0, 2); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Payload too long errors. + +// The decoder calls the listener's OnFrameSizeError method if the frame's +// payload is longer than the currently configured maximum payload size. +TEST_F(Http2FrameDecoderTest, BeyondMaximum) { + decoder_.set_maximum_payload_size(2); + const char kFrameData[] = { + '\x00', '\x00', '\x07', // Payload length: 7 + '\x00', // DATA + '\x09', // Flags: END_STREAM | PADDED + '\x00', '\x00', '\x00', '\x02', // Stream ID: 0 (REQUIRES ID) + '\x03', // Pad Len + 'a', 'b', 'c', // Data + '\x00', '\x00', '\x00', // Padding + }; + Http2FrameHeader header(7, Http2FrameType::DATA, + Http2FrameFlag::END_STREAM | Http2FrameFlag::PADDED, + 2); + FrameParts expected(header); + expected.SetHasFrameSizeError(true); + auto validator = [&expected, this](const DecodeBuffer& input, + DecodeStatus status) -> AssertionResult { + VERIFY_EQ(status, DecodeStatus::kDecodeError); + // The decoder detects this error after decoding the header, and without + // trying to decode the payload. + VERIFY_EQ(input.Offset(), Http2FrameHeader::EncodedSize()); + VERIFY_AND_RETURN_SUCCESS(VerifyCollected(expected)); + }; + ResetDecodeSpeedCounters(); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(ToStringPiece(kFrameData), + validator)); + EXPECT_GT(fast_decode_count_, 0u); + EXPECT_GT(slow_decode_count_, 0u); +} + +TEST_F(Http2FrameDecoderTest, PriorityTooLong) { + const char kFrameData[] = { + '\x00', '\x00', '\x06', // Length: 5 + '\x02', // Type: PRIORITY + '\x00', // Flags: none + '\x00', '\x00', '\x00', '\x02', // Stream: 2 + '\x80', '\x00', '\x00', '\x01', // Parent: 1 (Exclusive) + '\x10', // Weight: 17 + '\x00', // Too much + }; + Http2FrameHeader header(6, Http2FrameType::PRIORITY, 0, 2); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, RstStreamTooLong) { + const char kFrameData[] = { + '\x00', '\x00', '\x05', // Length: 4 + '\x03', // Type: RST_STREAM + '\x00', // Flags: none + '\x00', '\x00', '\x00', '\x01', // Stream: 1 + '\x00', '\x00', '\x00', '\x01', // Error: PROTOCOL_ERROR + '\x00', // Too much + }; + Http2FrameHeader header(5, Http2FrameType::RST_STREAM, 0, 1); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, SettingsAckTooLong) { + const char kFrameData[] = { + '\x00', '\x00', '\x06', // Length: 6 + '\x04', // Type: SETTINGS + '\x01', // Flags: ACK + '\x00', '\x00', '\x00', '\x00', // Stream: 0 + '\x00', '\x00', // Extra + '\x00', '\x00', '\x00', '\x00', // Extra + }; + Http2FrameHeader header(6, Http2FrameType::SETTINGS, Http2FrameFlag::ACK, 0); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, PingAckTooLong) { + const char kFrameData[] = { + '\x00', '\x00', '\x09', // Length: 8 + '\x06', // Type: PING + '\xff', // Flags: ACK | 0xfe + '\x00', '\x00', '\x00', '\x00', // Stream: 0 + 's', 'o', 'm', 'e', // "some" + 'd', 'a', 't', 'a', // "data" + '\x00', // Too much + }; + Http2FrameHeader header(9, Http2FrameType::PING, Http2FrameFlag::ACK, 0); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, WindowUpdateTooLong) { + const char kFrameData[] = { + '\x00', '\x00', '\x05', // Length: 5 + '\x08', // Type: WINDOW_UPDATE + '\x0f', // Flags: 0xff (no valid flags) + '\x00', '\x00', '\x00', '\x01', // Stream: 1 + '\x80', '\x00', '\x04', '\x00', // Incr: 1024 (plus R bit) + '\x00', // Too much + }; + Http2FrameHeader header(5, Http2FrameType::WINDOW_UPDATE, 0, 1); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/decoder/http2_structure_decoder.cc b/http2/decoder/http2_structure_decoder.cc new file mode 100644 index 0000000..e4f2c1a --- /dev/null +++ b/http2/decoder/http2_structure_decoder.cc
@@ -0,0 +1,90 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/http2_structure_decoder.h" + +#include <algorithm> + +#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h" + +namespace http2 { + +// Below we have some defensive coding: if we somehow run off the end, don't +// overwrite lots of memory. Note that most of this decoder is not defensive +// against bugs in the decoder, only against malicious encoders, but since +// we're copying memory into a buffer here, let's make sure we don't allow a +// small mistake to grow larger. The decoder will get stuck if we hit the +// HTTP2_BUG conditions, but shouldn't corrupt memory. + +uint32_t Http2StructureDecoder::IncompleteStart(DecodeBuffer* db, + uint32_t target_size) { + if (target_size > sizeof buffer_) { + HTTP2_BUG << "target_size too large for buffer: " << target_size; + return 0; + } + const uint32_t num_to_copy = db->MinLengthRemaining(target_size); + memcpy(buffer_, db->cursor(), num_to_copy); + offset_ = num_to_copy; + db->AdvanceCursor(num_to_copy); + return num_to_copy; +} + +DecodeStatus Http2StructureDecoder::IncompleteStart(DecodeBuffer* db, + uint32_t* remaining_payload, + uint32_t target_size) { + DVLOG(1) << "IncompleteStart@" << this + << ": *remaining_payload=" << *remaining_payload + << "; target_size=" << target_size + << "; db->Remaining=" << db->Remaining(); + *remaining_payload -= + IncompleteStart(db, std::min(target_size, *remaining_payload)); + if (*remaining_payload > 0 && db->Empty()) { + return DecodeStatus::kDecodeInProgress; + } + DVLOG(1) << "IncompleteStart: kDecodeError"; + return DecodeStatus::kDecodeError; +} + +bool Http2StructureDecoder::ResumeFillingBuffer(DecodeBuffer* db, + uint32_t target_size) { + DVLOG(2) << "ResumeFillingBuffer@" << this << ": target_size=" << target_size + << "; offset_=" << offset_ << "; db->Remaining=" << db->Remaining(); + if (target_size < offset_) { + HTTP2_BUG << "Already filled buffer_! target_size=" << target_size + << " offset_=" << offset_; + return false; + } + const uint32_t needed = target_size - offset_; + const uint32_t num_to_copy = db->MinLengthRemaining(needed); + DVLOG(2) << "ResumeFillingBuffer num_to_copy=" << num_to_copy; + memcpy(&buffer_[offset_], db->cursor(), num_to_copy); + db->AdvanceCursor(num_to_copy); + offset_ += num_to_copy; + return needed == num_to_copy; +} + +bool Http2StructureDecoder::ResumeFillingBuffer(DecodeBuffer* db, + uint32_t* remaining_payload, + uint32_t target_size) { + DVLOG(2) << "ResumeFillingBuffer@" << this << ": target_size=" << target_size + << "; offset_=" << offset_ + << "; *remaining_payload=" << *remaining_payload + << "; db->Remaining=" << db->Remaining(); + if (target_size < offset_) { + HTTP2_BUG << "Already filled buffer_! target_size=" << target_size + << " offset_=" << offset_; + return false; + } + const uint32_t needed = target_size - offset_; + const uint32_t num_to_copy = + db->MinLengthRemaining(std::min(needed, *remaining_payload)); + DVLOG(2) << "ResumeFillingBuffer num_to_copy=" << num_to_copy; + memcpy(&buffer_[offset_], db->cursor(), num_to_copy); + db->AdvanceCursor(num_to_copy); + offset_ += num_to_copy; + *remaining_payload -= num_to_copy; + return needed == num_to_copy; +} + +} // namespace http2
diff --git a/http2/decoder/http2_structure_decoder.h b/http2/decoder/http2_structure_decoder.h new file mode 100644 index 0000000..46c88b8 --- /dev/null +++ b/http2/decoder/http2_structure_decoder.h
@@ -0,0 +1,131 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_DECODER_HTTP2_STRUCTURE_DECODER_H_ +#define QUICHE_HTTP2_DECODER_HTTP2_STRUCTURE_DECODER_H_ + +// Http2StructureDecoder is a class for decoding the fixed size structures in +// the HTTP/2 spec, defined in net/third_party/quiche/src/http2/http2_structures.h. This class +// is in aid of deciding whether to keep the SlowDecode methods which I +// (jamessynge) now think may not be worth their complexity. In particular, +// if most transport buffers are large, so it is rare that a structure is +// split across buffer boundaries, than the cost of buffering upon +// those rare occurrences is small, which then simplifies the callers. + +#include <cstdint> + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_http2_structures.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" + +namespace http2 { +namespace test { +class Http2StructureDecoderPeer; +} // namespace test + +class HTTP2_EXPORT_PRIVATE Http2StructureDecoder { + public: + // The caller needs to keep track of whether to call Start or Resume. + // + // Start has an optimization for the case where the DecodeBuffer holds the + // entire encoded structure; in that case it decodes into *out and returns + // true, and does NOT touch the data members of the Http2StructureDecoder + // instance because the caller won't be calling Resume later. + // + // However, if the DecodeBuffer is too small to hold the entire encoded + // structure, Start copies the available bytes into the Http2StructureDecoder + // instance, and returns false to indicate that it has not been able to + // complete the decoding. + // + template <class S> + bool Start(S* out, DecodeBuffer* db) { + static_assert(S::EncodedSize() <= sizeof buffer_, "buffer_ is too small"); + DVLOG(2) << __func__ << "@" << this << ": db->Remaining=" << db->Remaining() + << "; EncodedSize=" << S::EncodedSize(); + if (db->Remaining() >= S::EncodedSize()) { + DoDecode(out, db); + return true; + } + IncompleteStart(db, S::EncodedSize()); + return false; + } + + template <class S> + bool Resume(S* out, DecodeBuffer* db) { + DVLOG(2) << __func__ << "@" << this << ": offset_=" << offset_ + << "; db->Remaining=" << db->Remaining(); + if (ResumeFillingBuffer(db, S::EncodedSize())) { + // We have the whole thing now. + DVLOG(2) << __func__ << "@" << this << " offset_=" << offset_ + << " Ready to decode from buffer_."; + DecodeBuffer buffer_db(buffer_, S::EncodedSize()); + DoDecode(out, &buffer_db); + return true; + } + DCHECK_LT(offset_, S::EncodedSize()); + return false; + } + + // A second pair of Start and Resume, where the caller has a variable, + // |remaining_payload| that is both tested for sufficiency and updated + // during decoding. Note that the decode buffer may extend beyond the + // remaining payload because the buffer may include padding. + template <class S> + DecodeStatus Start(S* out, DecodeBuffer* db, uint32_t* remaining_payload) { + static_assert(S::EncodedSize() <= sizeof buffer_, "buffer_ is too small"); + DVLOG(2) << __func__ << "@" << this + << ": *remaining_payload=" << *remaining_payload + << "; db->Remaining=" << db->Remaining() + << "; EncodedSize=" << S::EncodedSize(); + if (db->MinLengthRemaining(*remaining_payload) >= S::EncodedSize()) { + DoDecode(out, db); + *remaining_payload -= S::EncodedSize(); + return DecodeStatus::kDecodeDone; + } + return IncompleteStart(db, remaining_payload, S::EncodedSize()); + } + + template <class S> + bool Resume(S* out, DecodeBuffer* db, uint32_t* remaining_payload) { + DVLOG(3) << __func__ << "@" << this << ": offset_=" << offset_ + << "; *remaining_payload=" << *remaining_payload + << "; db->Remaining=" << db->Remaining() + << "; EncodedSize=" << S::EncodedSize(); + if (ResumeFillingBuffer(db, remaining_payload, S::EncodedSize())) { + // We have the whole thing now. + DVLOG(2) << __func__ << "@" << this << ": offset_=" << offset_ + << "; Ready to decode from buffer_."; + DecodeBuffer buffer_db(buffer_, S::EncodedSize()); + DoDecode(out, &buffer_db); + return true; + } + DCHECK_LT(offset_, S::EncodedSize()); + return false; + } + + uint32_t offset() const { return offset_; } + + private: + friend class test::Http2StructureDecoderPeer; + + uint32_t IncompleteStart(DecodeBuffer* db, uint32_t target_size); + DecodeStatus IncompleteStart(DecodeBuffer* db, + uint32_t* remaining_payload, + uint32_t target_size); + + bool ResumeFillingBuffer(DecodeBuffer* db, uint32_t target_size); + bool ResumeFillingBuffer(DecodeBuffer* db, + uint32_t* remaining_payload, + uint32_t target_size); + + uint32_t offset_; + char buffer_[Http2FrameHeader::EncodedSize()]; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_DECODER_HTTP2_STRUCTURE_DECODER_H_
diff --git a/http2/decoder/http2_structure_decoder_test.cc b/http2/decoder/http2_structure_decoder_test.cc new file mode 100644 index 0000000..8d8da7b --- /dev/null +++ b/http2/decoder/http2_structure_decoder_test.cc
@@ -0,0 +1,541 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/http2_structure_decoder.h" + +// Tests decoding all of the fixed size HTTP/2 structures (i.e. those defined in +// net/third_party/quiche/src/http2/http2_structures.h) using Http2StructureDecoder, which +// handles buffering of structures split across input buffer boundaries, and in +// turn uses DoDecode when it has all of a structure in a contiguous buffer. + +// NOTE: This tests the first pair of Start and Resume, which don't take +// a remaining_payload parameter. The other pair are well tested via the +// payload decoder tests, though... +// TODO(jamessynge): Create type parameterized tests for Http2StructureDecoder +// where the type is the type of structure, and with testing of both pairs of +// Start and Resume methods; note that it appears that the first pair will be +// used only for Http2FrameHeader, and the other pair only for structures in the +// frame payload. + +#include <stddef.h> + +#include <cstdint> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures_test_util.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_reconstruct_object.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" +#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +using ::testing::AssertionFailure; +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; + +namespace http2 { +namespace test { +namespace { +const bool kMayReturnZeroOnFirst = false; + +template <class S> +class Http2StructureDecoderTest : public RandomDecoderTest { + protected: + typedef S Structure; + + Http2StructureDecoderTest() { + // IF the test adds more data after the encoded structure, stop as + // soon as the structure is decoded. + stop_decode_on_done_ = true; + } + + DecodeStatus StartDecoding(DecodeBuffer* b) override { + // Overwrite the current contents of |structure_|, in to which we'll + // decode the buffer, so that we can be confident that we really decoded + // the structure every time. + Http2DefaultReconstructObject(&structure_, RandomPtr()); + uint32_t old_remaining = b->Remaining(); + if (structure_decoder_.Start(&structure_, b)) { + EXPECT_EQ(old_remaining - S::EncodedSize(), b->Remaining()); + ++fast_decode_count_; + return DecodeStatus::kDecodeDone; + } else { + EXPECT_LT(structure_decoder_.offset(), S::EncodedSize()); + EXPECT_EQ(0u, b->Remaining()); + EXPECT_EQ(old_remaining - structure_decoder_.offset(), b->Remaining()); + ++incomplete_start_count_; + return DecodeStatus::kDecodeInProgress; + } + } + + DecodeStatus ResumeDecoding(DecodeBuffer* b) override { + uint32_t old_offset = structure_decoder_.offset(); + EXPECT_LT(old_offset, S::EncodedSize()); + uint32_t avail = b->Remaining(); + if (structure_decoder_.Resume(&structure_, b)) { + EXPECT_LE(S::EncodedSize(), old_offset + avail); + EXPECT_EQ(b->Remaining(), avail - (S::EncodedSize() - old_offset)); + ++slow_decode_count_; + return DecodeStatus::kDecodeDone; + } else { + EXPECT_LT(structure_decoder_.offset(), S::EncodedSize()); + EXPECT_EQ(0u, b->Remaining()); + EXPECT_GT(S::EncodedSize(), old_offset + avail); + ++incomplete_resume_count_; + return DecodeStatus::kDecodeInProgress; + } + } + + // Fully decodes the Structure at the start of data, and confirms it matches + // *expected (if provided). + AssertionResult DecodeLeadingStructure(const S* expected, + Http2StringPiece data) { + VERIFY_LE(S::EncodedSize(), data.size()); + DecodeBuffer original(data); + + // 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 structure_ matches the expected value, if provided. + Validator validator; + if (expected != nullptr) { + validator = [expected, this](const DecodeBuffer& db, + DecodeStatus status) -> AssertionResult { + VERIFY_EQ(*expected, structure_); + return AssertionSuccess(); + }; + } + + // Before that, validate that decoding is done and that we've advanced + // the cursor the expected amount. + validator = ValidateDoneAndOffset(S::EncodedSize(), validator); + + // Decode several times, with several segmentations of the input buffer. + fast_decode_count_ = 0; + slow_decode_count_ = 0; + incomplete_start_count_ = 0; + incomplete_resume_count_ = 0; + VERIFY_SUCCESS(DecodeAndValidateSeveralWays( + &original, kMayReturnZeroOnFirst, validator)); + VERIFY_FALSE(HasFailure()); + VERIFY_EQ(S::EncodedSize(), structure_decoder_.offset()); + VERIFY_EQ(S::EncodedSize(), original.Offset()); + VERIFY_LT(0u, fast_decode_count_); + VERIFY_LT(0u, slow_decode_count_); + VERIFY_LT(0u, incomplete_start_count_); + + // If the structure is large enough so that SelectZeroOrOne will have + // caused Resume to return false, check that occurred. + if (S::EncodedSize() >= 2) { + VERIFY_LE(0u, incomplete_resume_count_); + } else { + VERIFY_EQ(0u, incomplete_resume_count_); + } + if (expected != nullptr) { + DVLOG(1) << "DecodeLeadingStructure expected: " << *expected; + DVLOG(1) << "DecodeLeadingStructure actual: " << structure_; + VERIFY_EQ(*expected, structure_); + } + return AssertionSuccess(); + } + + template <size_t N> + AssertionResult DecodeLeadingStructure(const char (&data)[N]) { + VERIFY_AND_RETURN_SUCCESS( + DecodeLeadingStructure(nullptr, Http2StringPiece(data, N))); + } + + template <size_t N> + AssertionResult DecodeLeadingStructure(const unsigned char (&data)[N]) { + VERIFY_AND_RETURN_SUCCESS( + DecodeLeadingStructure(nullptr, ToStringPiece(data))); + } + + // Encode the structure |in_s| into bytes, then decode the bytes + // and validate that the decoder produced the same field values. + AssertionResult EncodeThenDecode(const S& in_s) { + Http2String bytes = SerializeStructure(in_s); + VERIFY_EQ(S::EncodedSize(), bytes.size()); + VERIFY_AND_RETURN_SUCCESS(DecodeLeadingStructure(&in_s, bytes)); + } + + // Repeatedly fill a structure with random but valid contents, encode it, then + // decode it, and finally validate that the decoded structure matches the + // random input. Lather-rinse-and-repeat. + AssertionResult TestDecodingRandomizedStructures(size_t count) { + for (size_t i = 0; i < count; ++i) { + Structure input; + Randomize(&input, RandomPtr()); + VERIFY_SUCCESS(EncodeThenDecode(input)); + } + return AssertionSuccess(); + } + + AssertionResult TestDecodingRandomizedStructures() { + VERIFY_SUCCESS(TestDecodingRandomizedStructures(100)); + return AssertionSuccess(); + } + + uint32_t decode_offset_ = 0; + S structure_; + Http2StructureDecoder structure_decoder_; + size_t fast_decode_count_ = 0; + size_t slow_decode_count_ = 0; + size_t incomplete_start_count_ = 0; + size_t incomplete_resume_count_ = 0; +}; + +class Http2FrameHeaderDecoderTest + : public Http2StructureDecoderTest<Http2FrameHeader> {}; + +TEST_F(Http2FrameHeaderDecoderTest, DecodesLiteral) { + { + // Realistic input. + // clang-format off + const char kData[] = { + 0x00, 0x00, 0x05, // Payload length: 5 + 0x01, // Frame type: HEADERS + 0x08, // Flags: PADDED + 0x00, 0x00, 0x00, 0x01, // Stream ID: 1 + 0x04, // Padding length: 4 + 0x00, 0x00, 0x00, 0x00, // Padding bytes + }; + // clang-format on + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(5u, structure_.payload_length); + EXPECT_EQ(Http2FrameType::HEADERS, structure_.type); + EXPECT_EQ(Http2FrameFlag::PADDED, structure_.flags); + EXPECT_EQ(1u, structure_.stream_id); + } + { + // Unlikely input. + // clang-format off + const unsigned char kData[] = { + 0xff, 0xff, 0xff, // Payload length: uint24 max + 0xff, // Frame type: Unknown + 0xff, // Flags: Unknown/All + 0xff, 0xff, 0xff, 0xff, // Stream ID: uint31 max, plus R-bit + }; + // clang-format on + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ((1u << 24) - 1u, structure_.payload_length); + EXPECT_EQ(static_cast<Http2FrameType>(255), structure_.type); + EXPECT_EQ(255, structure_.flags); + EXPECT_EQ(0x7FFFFFFFu, structure_.stream_id); + } +} + +TEST_F(Http2FrameHeaderDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class Http2PriorityFieldsDecoderTest + : public Http2StructureDecoderTest<Http2PriorityFields> {}; + +TEST_F(Http2PriorityFieldsDecoderTest, DecodesLiteral) { + { + // clang-format off + const unsigned char kData[] = { + 0x80, 0x00, 0x00, 0x05, // Exclusive (yes) and Dependency (5) + 0xff, // Weight: 256 (after adding 1) + }; + // clang-format on + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(5u, structure_.stream_dependency); + EXPECT_EQ(256u, structure_.weight); + EXPECT_EQ(true, structure_.is_exclusive); + } + { + // clang-format off + const unsigned char kData[] = { + 0x7f, 0xff, 0xff, 0xff, // Excl. (no) and Dependency (uint31 max) + 0x00, // Weight: 1 (after adding 1) + }; + // clang-format on + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(StreamIdMask(), structure_.stream_dependency); + EXPECT_EQ(1u, structure_.weight); + EXPECT_FALSE(structure_.is_exclusive); + } +} + +TEST_F(Http2PriorityFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class Http2RstStreamFieldsDecoderTest + : public Http2StructureDecoderTest<Http2RstStreamFields> {}; + +TEST_F(Http2RstStreamFieldsDecoderTest, DecodesLiteral) { + { + // clang-format off + const char kData[] = { + 0x00, 0x00, 0x00, 0x01, // Error: PROTOCOL_ERROR + }; + // clang-format on + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_TRUE(structure_.IsSupportedErrorCode()); + EXPECT_EQ(Http2ErrorCode::PROTOCOL_ERROR, structure_.error_code); + } + { + // clang-format off + const unsigned char kData[] = { + 0xff, 0xff, 0xff, 0xff, // Error: max uint32 (Unknown error code) + }; + // clang-format on + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_FALSE(structure_.IsSupportedErrorCode()); + EXPECT_EQ(static_cast<Http2ErrorCode>(0xffffffff), structure_.error_code); + } +} + +TEST_F(Http2RstStreamFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class Http2SettingFieldsDecoderTest + : public Http2StructureDecoderTest<Http2SettingFields> {}; + +TEST_F(Http2SettingFieldsDecoderTest, DecodesLiteral) { + { + // clang-format off + const char kData[] = { + 0x00, 0x01, // Setting: HEADER_TABLE_SIZE + 0x00, 0x00, 0x40, 0x00, // Value: 16K + }; + // clang-format on + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_TRUE(structure_.IsSupportedParameter()); + EXPECT_EQ(Http2SettingsParameter::HEADER_TABLE_SIZE, structure_.parameter); + EXPECT_EQ(1u << 14, structure_.value); + } + { + // clang-format off + const unsigned char kData[] = { + 0x00, 0x00, // Setting: Unknown (0) + 0xff, 0xff, 0xff, 0xff, // Value: max uint32 + }; + // clang-format on + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_FALSE(structure_.IsSupportedParameter()); + EXPECT_EQ(static_cast<Http2SettingsParameter>(0), structure_.parameter); + } +} + +TEST_F(Http2SettingFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class Http2PushPromiseFieldsDecoderTest + : public Http2StructureDecoderTest<Http2PushPromiseFields> {}; + +TEST_F(Http2PushPromiseFieldsDecoderTest, DecodesLiteral) { + { + // clang-format off + const unsigned char kData[] = { + 0x00, 0x01, 0x8a, 0x92, // Promised Stream ID: 101010 + }; + // clang-format on + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(101010u, structure_.promised_stream_id); + } + { + // Promised stream id has R-bit (reserved for future use) set, which + // should be cleared by the decoder. + // clang-format off + const unsigned char kData[] = { + // Promised Stream ID: max uint31 and R-bit + 0xff, 0xff, 0xff, 0xff, + }; + // clang-format on + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(StreamIdMask(), structure_.promised_stream_id); + } +} + +TEST_F(Http2PushPromiseFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class Http2PingFieldsDecoderTest + : public Http2StructureDecoderTest<Http2PingFields> {}; + +TEST_F(Http2PingFieldsDecoderTest, DecodesLiteral) { + { + // Each byte is different, so can detect if order changed. + const char kData[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(ToStringPiece(kData), ToStringPiece(structure_.opaque_bytes)); + } + { + // All zeros, detect problems handling NULs. + const char kData[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(ToStringPiece(kData), ToStringPiece(structure_.opaque_bytes)); + } + { + const unsigned char kData[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(ToStringPiece(kData), ToStringPiece(structure_.opaque_bytes)); + } +} + +TEST_F(Http2PingFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class Http2GoAwayFieldsDecoderTest + : public Http2StructureDecoderTest<Http2GoAwayFields> {}; + +TEST_F(Http2GoAwayFieldsDecoderTest, DecodesLiteral) { + { + // clang-format off + const char kData[] = { + 0x00, 0x00, 0x00, 0x00, // Last Stream ID: 0 + 0x00, 0x00, 0x00, 0x00, // Error: NO_ERROR (0) + }; + // clang-format on + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(0u, structure_.last_stream_id); + EXPECT_TRUE(structure_.IsSupportedErrorCode()); + EXPECT_EQ(Http2ErrorCode::HTTP2_NO_ERROR, structure_.error_code); + } + { + // clang-format off + const char kData[] = { + 0x00, 0x00, 0x00, 0x01, // Last Stream ID: 1 + 0x00, 0x00, 0x00, 0x0d, // Error: HTTP_1_1_REQUIRED + }; + // clang-format on + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(1u, structure_.last_stream_id); + EXPECT_TRUE(structure_.IsSupportedErrorCode()); + EXPECT_EQ(Http2ErrorCode::HTTP_1_1_REQUIRED, structure_.error_code); + } + { + // clang-format off + const unsigned char kData[] = { + 0xff, 0xff, 0xff, 0xff, // Last Stream ID: max uint31 and R-bit + 0xff, 0xff, 0xff, 0xff, // Error: max uint32 (Unknown error code) + }; + // clang-format on + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(StreamIdMask(), structure_.last_stream_id); // No high-bit. + EXPECT_FALSE(structure_.IsSupportedErrorCode()); + EXPECT_EQ(static_cast<Http2ErrorCode>(0xffffffff), structure_.error_code); + } +} + +TEST_F(Http2GoAwayFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class Http2WindowUpdateFieldsDecoderTest + : public Http2StructureDecoderTest<Http2WindowUpdateFields> {}; + +TEST_F(Http2WindowUpdateFieldsDecoderTest, DecodesLiteral) { + { + // clang-format off + const char kData[] = { + 0x00, 0x01, 0x00, 0x00, // Window Size Increment: 2 ^ 16 + }; + // clang-format on + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(1u << 16, structure_.window_size_increment); + } + { + // Increment must be non-zero, but we need to be able to decode the invalid + // zero to detect it. + // clang-format off + const char kData[] = { + 0x00, 0x00, 0x00, 0x00, // Window Size Increment: 0 + }; + // clang-format on + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(0u, structure_.window_size_increment); + } + { + // Increment has R-bit (reserved for future use) set, which + // should be cleared by the decoder. + // clang-format off + const unsigned char kData[] = { + // Window Size Increment: max uint31 and R-bit + 0xff, 0xff, 0xff, 0xff, + }; + // clang-format on + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(StreamIdMask(), structure_.window_size_increment); + } +} + +TEST_F(Http2WindowUpdateFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class Http2AltSvcFieldsDecoderTest + : public Http2StructureDecoderTest<Http2AltSvcFields> {}; + +TEST_F(Http2AltSvcFieldsDecoderTest, DecodesLiteral) { + { + // clang-format off + const char kData[] = { + 0x00, 0x00, // Origin Length: 0 + }; + // clang-format on + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(0, structure_.origin_length); + } + { + // clang-format off + const char kData[] = { + 0x00, 0x14, // Origin Length: 20 + }; + // clang-format on + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(20, structure_.origin_length); + } + { + // clang-format off + const unsigned char kData[] = { + 0xff, 0xff, // Origin Length: uint16 max + }; + // clang-format on + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(65535, structure_.origin_length); + } +} + +TEST_F(Http2AltSvcFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/decoder/http2_structure_decoder_test_util.cc b/http2/decoder/http2_structure_decoder_test_util.cc new file mode 100644 index 0000000..90805ea --- /dev/null +++ b/http2/decoder/http2_structure_decoder_test_util.cc
@@ -0,0 +1,22 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test_util.h" + +#include <cstddef> + +namespace http2 { +namespace test { + +// static +void Http2StructureDecoderPeer::Randomize(Http2StructureDecoder* p, + Http2Random* rng) { + p->offset_ = rng->Rand32(); + for (size_t i = 0; i < sizeof p->buffer_; ++i) { + p->buffer_[i] = rng->Rand8(); + } +} + +} // namespace test +} // namespace http2
diff --git a/http2/decoder/http2_structure_decoder_test_util.h b/http2/decoder/http2_structure_decoder_test_util.h new file mode 100644 index 0000000..4b533c7 --- /dev/null +++ b/http2/decoder/http2_structure_decoder_test_util.h
@@ -0,0 +1,24 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_DECODER_HTTP2_STRUCTURE_DECODER_TEST_UTIL_H_ +#define QUICHE_HTTP2_DECODER_HTTP2_STRUCTURE_DECODER_TEST_UTIL_H_ + +#include "net/third_party/quiche/src/http2/decoder/http2_structure_decoder.h" + +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" + +namespace http2 { +namespace test { + +class Http2StructureDecoderPeer { + public: + // Overwrite the Http2StructureDecoder instance with random values. + static void Randomize(Http2StructureDecoder* p, Http2Random* rng); +}; + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_DECODER_HTTP2_STRUCTURE_DECODER_TEST_UTIL_H_
diff --git a/http2/decoder/payload_decoders/altsvc_payload_decoder.cc b/http2/decoder/payload_decoders/altsvc_payload_decoder.cc new file mode 100644 index 0000000..4e4d860 --- /dev/null +++ b/http2/decoder/payload_decoders/altsvc_payload_decoder.cc
@@ -0,0 +1,148 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h" + +namespace http2 { + +std::ostream& operator<<(std::ostream& out, + AltSvcPayloadDecoder::PayloadState v) { + switch (v) { + case AltSvcPayloadDecoder::PayloadState::kStartDecodingStruct: + return out << "kStartDecodingStruct"; + case AltSvcPayloadDecoder::PayloadState::kMaybeDecodedStruct: + return out << "kMaybeDecodedStruct"; + case AltSvcPayloadDecoder::PayloadState::kDecodingStrings: + return out << "kDecodingStrings"; + case AltSvcPayloadDecoder::PayloadState::kResumeDecodingStruct: + return out << "kResumeDecodingStruct"; + } + // Since the value doesn't come over the wire, only a programming bug should + // result in reaching this point. + int unknown = static_cast<int>(v); + HTTP2_BUG << "Invalid AltSvcPayloadDecoder::PayloadState: " << unknown; + return out << "AltSvcPayloadDecoder::PayloadState(" << unknown << ")"; +} + +DecodeStatus AltSvcPayloadDecoder::StartDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "AltSvcPayloadDecoder::StartDecodingPayload: " + << state->frame_header(); + DCHECK_EQ(Http2FrameType::ALTSVC, state->frame_header().type); + DCHECK_LE(db->Remaining(), state->frame_header().payload_length); + DCHECK_EQ(0, state->frame_header().flags); + + state->InitializeRemainders(); + payload_state_ = PayloadState::kStartDecodingStruct; + + return ResumeDecodingPayload(state, db); +} + +DecodeStatus AltSvcPayloadDecoder::ResumeDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + const Http2FrameHeader& frame_header = state->frame_header(); + DVLOG(2) << "AltSvcPayloadDecoder::ResumeDecodingPayload: " << frame_header; + DCHECK_EQ(Http2FrameType::ALTSVC, frame_header.type); + DCHECK_LE(state->remaining_payload(), frame_header.payload_length); + DCHECK_LE(db->Remaining(), state->remaining_payload()); + DCHECK_NE(PayloadState::kMaybeDecodedStruct, payload_state_); + // |status| has to be initialized to some value to avoid compiler error in + // case PayloadState::kMaybeDecodedStruct below, but value does not matter, + // see DCHECK_NE above. + DecodeStatus status = DecodeStatus::kDecodeError; + while (true) { + DVLOG(2) << "AltSvcPayloadDecoder::ResumeDecodingPayload payload_state_=" + << payload_state_; + switch (payload_state_) { + case PayloadState::kStartDecodingStruct: + status = state->StartDecodingStructureInPayload(&altsvc_fields_, db); + HTTP2_FALLTHROUGH; + + case PayloadState::kMaybeDecodedStruct: + if (status == DecodeStatus::kDecodeDone && + altsvc_fields_.origin_length <= state->remaining_payload()) { + size_t origin_length = altsvc_fields_.origin_length; + size_t value_length = state->remaining_payload() - origin_length; + state->listener()->OnAltSvcStart(frame_header, origin_length, + value_length); + } else if (status != DecodeStatus::kDecodeDone) { + DCHECK(state->remaining_payload() > 0 || + status == DecodeStatus::kDecodeError) + << "\nremaining_payload: " << state->remaining_payload() + << "\nstatus: " << status << "\nheader: " << frame_header; + // Assume in progress. + payload_state_ = PayloadState::kResumeDecodingStruct; + return status; + } else { + // The origin's length is longer than the remaining payload. + DCHECK_GT(altsvc_fields_.origin_length, state->remaining_payload()); + return state->ReportFrameSizeError(); + } + HTTP2_FALLTHROUGH; + + case PayloadState::kDecodingStrings: + return DecodeStrings(state, db); + + case PayloadState::kResumeDecodingStruct: + status = state->ResumeDecodingStructureInPayload(&altsvc_fields_, db); + payload_state_ = PayloadState::kMaybeDecodedStruct; + continue; + } + HTTP2_BUG << "PayloadState: " << payload_state_; + } +} + +DecodeStatus AltSvcPayloadDecoder::DecodeStrings(FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "AltSvcPayloadDecoder::DecodeStrings remaining_payload=" + << state->remaining_payload() + << ", db->Remaining=" << db->Remaining(); + // Note that we don't explicitly keep track of exactly how far through the + // origin; instead we compute it from how much is left of the original + // payload length and the decoded total length of the origin. + size_t origin_length = altsvc_fields_.origin_length; + size_t value_length = state->frame_header().payload_length - origin_length - + Http2AltSvcFields::EncodedSize(); + if (state->remaining_payload() > value_length) { + size_t remaining_origin_length = state->remaining_payload() - value_length; + size_t avail = db->MinLengthRemaining(remaining_origin_length); + state->listener()->OnAltSvcOriginData(db->cursor(), avail); + db->AdvanceCursor(avail); + state->ConsumePayload(avail); + if (remaining_origin_length > avail) { + payload_state_ = PayloadState::kDecodingStrings; + return DecodeStatus::kDecodeInProgress; + } + } + // All that is left is the value string. + DCHECK_LE(state->remaining_payload(), value_length); + DCHECK_LE(db->Remaining(), state->remaining_payload()); + if (db->HasData()) { + size_t avail = db->Remaining(); + state->listener()->OnAltSvcValueData(db->cursor(), avail); + db->AdvanceCursor(avail); + state->ConsumePayload(avail); + } + if (state->remaining_payload() == 0) { + state->listener()->OnAltSvcEnd(); + return DecodeStatus::kDecodeDone; + } + payload_state_ = PayloadState::kDecodingStrings; + return DecodeStatus::kDecodeInProgress; +} + +} // namespace http2
diff --git a/http2/decoder/payload_decoders/altsvc_payload_decoder.h b/http2/decoder/payload_decoders/altsvc_payload_decoder.h new file mode 100644 index 0000000..e3523f9 --- /dev/null +++ b/http2/decoder/payload_decoders/altsvc_payload_decoder.h
@@ -0,0 +1,64 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_ALTSVC_PAYLOAD_DECODER_H_ +#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_ALTSVC_PAYLOAD_DECODER_H_ + +// Decodes the payload of a ALTSVC frame. + +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" + +namespace http2 { +namespace test { +class AltSvcPayloadDecoderPeer; +} // namespace test + +class HTTP2_EXPORT_PRIVATE AltSvcPayloadDecoder { + public: + // States during decoding of a ALTSVC frame. + enum class PayloadState { + // Start decoding the fixed size structure at the start of an ALTSVC + // frame (Http2AltSvcFields). + kStartDecodingStruct, + + // Handle the DecodeStatus returned from starting or resuming the + // decoding of Http2AltSvcFields. If complete, calls OnAltSvcStart. + kMaybeDecodedStruct, + + // Reports the value of the strings (origin and value) of an ALTSVC frame + // to the listener. + kDecodingStrings, + + // The initial decode buffer wasn't large enough for the Http2AltSvcFields, + // so this state resumes the decoding when ResumeDecodingPayload is called + // later with a new DecodeBuffer. + kResumeDecodingStruct, + }; + + // Starts the decoding of a ALTSVC frame's payload, and completes it if the + // entire payload is in the provided decode buffer. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a ALTSVC frame's payload that has been split across + // decode buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); + + private: + friend class test::AltSvcPayloadDecoderPeer; + + // Implements state kDecodingStrings. + DecodeStatus DecodeStrings(FrameDecoderState* state, DecodeBuffer* db); + + Http2AltSvcFields altsvc_fields_; + PayloadState payload_state_; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_ALTSVC_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/altsvc_payload_decoder_test.cc b/http2/decoder/payload_decoders/altsvc_payload_decoder_test.cc new file mode 100644 index 0000000..bf928d3 --- /dev/null +++ b/http2/decoder/payload_decoders/altsvc_payload_decoder_test.cc
@@ -0,0 +1,121 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures_test_util.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +namespace http2 { +namespace test { + +// Provides friend access to an instance of the payload decoder, and also +// provides info to aid in testing. +class AltSvcPayloadDecoderPeer { + public: + static constexpr Http2FrameType FrameType() { return Http2FrameType::ALTSVC; } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; } +}; + +namespace { + +struct Listener : public FramePartsCollector { + void OnAltSvcStart(const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) override { + VLOG(1) << "OnAltSvcStart header: " << header + << "; origin_length=" << origin_length + << "; value_length=" << value_length; + StartFrame(header)->OnAltSvcStart(header, origin_length, value_length); + } + + void OnAltSvcOriginData(const char* data, size_t len) override { + VLOG(1) << "OnAltSvcOriginData: len=" << len; + CurrentFrame()->OnAltSvcOriginData(data, len); + } + + void OnAltSvcValueData(const char* data, size_t len) override { + VLOG(1) << "OnAltSvcValueData: len=" << len; + CurrentFrame()->OnAltSvcValueData(data, len); + } + + void OnAltSvcEnd() override { + VLOG(1) << "OnAltSvcEnd"; + EndFrame()->OnAltSvcEnd(); + } + + void OnFrameSizeError(const Http2FrameHeader& header) override { + VLOG(1) << "OnFrameSizeError: " << header; + FrameError(header)->OnFrameSizeError(header); + } +}; + +class AltSvcPayloadDecoderTest + : public AbstractPayloadDecoderTest<AltSvcPayloadDecoder, + AltSvcPayloadDecoderPeer, + Listener> {}; + +// Confirm we get an error if the payload is not long enough to hold +// Http2AltSvcFields and the indicated length of origin. +TEST_F(AltSvcPayloadDecoderTest, Truncated) { + Http2FrameBuilder fb; + fb.Append(Http2AltSvcFields{0xffff}); // The longest possible origin length. + fb.Append("Too little origin!"); + EXPECT_TRUE( + VerifyDetectsFrameSizeError(0, fb.buffer(), /*approve_size*/ nullptr)); +} + +class AltSvcPayloadLengthTests : public AltSvcPayloadDecoderTest, + public ::testing::WithParamInterface< + ::testing::tuple<uint16_t, uint32_t>> { + protected: + AltSvcPayloadLengthTests() + : origin_length_(::testing::get<0>(GetParam())), + value_length_(::testing::get<1>(GetParam())) { + VLOG(1) << "################ origin_length_=" << origin_length_ + << " value_length_=" << value_length_ << " ################"; + } + + const uint16_t origin_length_; + const uint32_t value_length_; +}; + +INSTANTIATE_TEST_CASE_P(VariousOriginAndValueLengths, + AltSvcPayloadLengthTests, + ::testing::Combine(::testing::Values(0, 1, 3, 65535), + ::testing::Values(0, 1, 3, 65537))); + +TEST_P(AltSvcPayloadLengthTests, ValidOriginAndValueLength) { + Http2String origin = Random().RandString(origin_length_); + Http2String value = Random().RandString(value_length_); + Http2FrameBuilder fb; + fb.Append(Http2AltSvcFields{origin_length_}); + fb.Append(origin); + fb.Append(value); + Http2FrameHeader header(fb.size(), Http2FrameType::ALTSVC, RandFlags(), + RandStreamId()); + set_frame_header(header); + FrameParts expected(header); + expected.SetAltSvcExpected(origin, value); + ASSERT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected)); +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/decoder/payload_decoders/continuation_payload_decoder.cc b/http2/decoder/payload_decoders/continuation_payload_decoder.cc new file mode 100644 index 0000000..aa9c817 --- /dev/null +++ b/http2/decoder/payload_decoders/continuation_payload_decoder.cc
@@ -0,0 +1,58 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" + +namespace http2 { + +DecodeStatus ContinuationPayloadDecoder::StartDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + const Http2FrameHeader& frame_header = state->frame_header(); + const uint32_t total_length = frame_header.payload_length; + + DVLOG(2) << "ContinuationPayloadDecoder::StartDecodingPayload: " + << frame_header; + DCHECK_EQ(Http2FrameType::CONTINUATION, frame_header.type); + DCHECK_LE(db->Remaining(), total_length); + DCHECK_EQ(0, frame_header.flags & ~(Http2FrameFlag::END_HEADERS)); + + state->InitializeRemainders(); + state->listener()->OnContinuationStart(frame_header); + return ResumeDecodingPayload(state, db); +} + +DecodeStatus ContinuationPayloadDecoder::ResumeDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "ContinuationPayloadDecoder::ResumeDecodingPayload" + << " remaining_payload=" << state->remaining_payload() + << " db->Remaining=" << db->Remaining(); + DCHECK_EQ(Http2FrameType::CONTINUATION, state->frame_header().type); + DCHECK_LE(state->remaining_payload(), state->frame_header().payload_length); + DCHECK_LE(db->Remaining(), state->remaining_payload()); + + size_t avail = db->Remaining(); + DCHECK_LE(avail, state->remaining_payload()); + if (avail > 0) { + state->listener()->OnHpackFragment(db->cursor(), avail); + db->AdvanceCursor(avail); + state->ConsumePayload(avail); + } + if (state->remaining_payload() == 0) { + state->listener()->OnContinuationEnd(); + return DecodeStatus::kDecodeDone; + } + return DecodeStatus::kDecodeInProgress; +} + +} // namespace http2
diff --git a/http2/decoder/payload_decoders/continuation_payload_decoder.h b/http2/decoder/payload_decoders/continuation_payload_decoder.h new file mode 100644 index 0000000..63b16ae --- /dev/null +++ b/http2/decoder/payload_decoders/continuation_payload_decoder.h
@@ -0,0 +1,31 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_CONTINUATION_PAYLOAD_DECODER_H_ +#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_CONTINUATION_PAYLOAD_DECODER_H_ + +// Decodes the payload of a CONTINUATION frame. + +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" + +namespace http2 { + +class HTTP2_EXPORT_PRIVATE ContinuationPayloadDecoder { + public: + // Starts the decoding of a CONTINUATION frame's payload, and completes + // it if the entire payload is in the provided decode buffer. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a CONTINUATION frame's payload that has been split across + // decode buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_CONTINUATION_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/continuation_payload_decoder_test.cc b/http2/decoder/payload_decoders/continuation_payload_decoder_test.cc new file mode 100644 index 0000000..63888d3 --- /dev/null +++ b/http2/decoder/payload_decoders/continuation_payload_decoder_test.cc
@@ -0,0 +1,85 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder.h" + +#include <stddef.h> + +#include <type_traits> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +namespace http2 { +namespace test { + +// Provides friend access to an instance of the payload decoder, and also +// provides info to aid in testing. +class ContinuationPayloadDecoderPeer { + public: + static constexpr Http2FrameType FrameType() { + return Http2FrameType::CONTINUATION; + } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; } +}; + +namespace { + +struct Listener : public FramePartsCollector { + void OnContinuationStart(const Http2FrameHeader& header) override { + VLOG(1) << "OnContinuationStart: " << header; + StartFrame(header)->OnContinuationStart(header); + } + + void OnHpackFragment(const char* data, size_t len) override { + VLOG(1) << "OnHpackFragment: len=" << len; + CurrentFrame()->OnHpackFragment(data, len); + } + + void OnContinuationEnd() override { + VLOG(1) << "OnContinuationEnd"; + EndFrame()->OnContinuationEnd(); + } +}; + +class ContinuationPayloadDecoderTest + : public AbstractPayloadDecoderTest<ContinuationPayloadDecoder, + ContinuationPayloadDecoderPeer, + Listener>, + public ::testing::WithParamInterface<uint32_t> { + protected: + ContinuationPayloadDecoderTest() : length_(GetParam()) { + VLOG(1) << "################ length_=" << length_ << " ################"; + } + + const uint32_t length_; +}; + +INSTANTIATE_TEST_CASE_P(VariousLengths, + ContinuationPayloadDecoderTest, + ::testing::Values(0, 1, 2, 3, 4, 5, 6)); + +TEST_P(ContinuationPayloadDecoderTest, ValidLength) { + Http2String hpack_payload = Random().RandString(length_); + Http2FrameHeader frame_header(length_, Http2FrameType::CONTINUATION, + RandFlags(), RandStreamId()); + set_frame_header(frame_header); + FrameParts expected(frame_header, hpack_payload); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(hpack_payload, expected)); +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/decoder/payload_decoders/data_payload_decoder.cc b/http2/decoder/payload_decoders/data_payload_decoder.cc new file mode 100644 index 0000000..b6b8041 --- /dev/null +++ b/http2/decoder/payload_decoders/data_payload_decoder.cc
@@ -0,0 +1,127 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h" + +namespace http2 { + +std::ostream& operator<<(std::ostream& out, + DataPayloadDecoder::PayloadState v) { + switch (v) { + case DataPayloadDecoder::PayloadState::kReadPadLength: + return out << "kReadPadLength"; + case DataPayloadDecoder::PayloadState::kReadPayload: + return out << "kReadPayload"; + case DataPayloadDecoder::PayloadState::kSkipPadding: + return out << "kSkipPadding"; + } + // Since the value doesn't come over the wire, only a programming bug should + // result in reaching this point. + int unknown = static_cast<int>(v); + HTTP2_BUG << "Invalid DataPayloadDecoder::PayloadState: " << unknown; + return out << "DataPayloadDecoder::PayloadState(" << unknown << ")"; +} + +DecodeStatus DataPayloadDecoder::StartDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db) { + const Http2FrameHeader& frame_header = state->frame_header(); + const uint32_t total_length = frame_header.payload_length; + + DVLOG(2) << "DataPayloadDecoder::StartDecodingPayload: " << frame_header; + DCHECK_EQ(Http2FrameType::DATA, frame_header.type); + DCHECK_LE(db->Remaining(), total_length); + DCHECK_EQ(0, frame_header.flags & + ~(Http2FrameFlag::END_STREAM | Http2FrameFlag::PADDED)); + + // Special case for the hoped for common case: unpadded and fits fully into + // the decode buffer. TO BE SEEN if that is true. It certainly requires that + // the transport buffers be large (e.g. >> 16KB typically). + // TODO(jamessynge) Add counters. + DVLOG(2) << "StartDecodingPayload total_length=" << total_length; + if (!frame_header.IsPadded()) { + DVLOG(2) << "StartDecodingPayload !IsPadded"; + if (db->Remaining() == total_length) { + DVLOG(2) << "StartDecodingPayload all present"; + // Note that we don't cache the listener field so that the callee can + // replace it if the frame is bad. + // If this case is common enough, consider combining the 3 callbacks + // into one. + state->listener()->OnDataStart(frame_header); + if (total_length > 0) { + state->listener()->OnDataPayload(db->cursor(), total_length); + db->AdvanceCursor(total_length); + } + state->listener()->OnDataEnd(); + return DecodeStatus::kDecodeDone; + } + payload_state_ = PayloadState::kReadPayload; + } else { + payload_state_ = PayloadState::kReadPadLength; + } + state->InitializeRemainders(); + state->listener()->OnDataStart(frame_header); + return ResumeDecodingPayload(state, db); +} + +DecodeStatus DataPayloadDecoder::ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "DataPayloadDecoder::ResumeDecodingPayload payload_state_=" + << payload_state_; + const Http2FrameHeader& frame_header = state->frame_header(); + DCHECK_EQ(Http2FrameType::DATA, frame_header.type); + DCHECK_LE(state->remaining_payload_and_padding(), + frame_header.payload_length); + DCHECK_LE(db->Remaining(), state->remaining_payload_and_padding()); + DecodeStatus status; + size_t avail; + switch (payload_state_) { + case PayloadState::kReadPadLength: + // ReadPadLength handles the OnPadLength callback, and updating the + // remaining_payload and remaining_padding fields. If the amount of + // padding is too large to fit in the frame's payload, ReadPadLength + // instead calls OnPaddingTooLong and returns kDecodeError. + status = state->ReadPadLength(db, /*report_pad_length*/ true); + if (status != DecodeStatus::kDecodeDone) { + return status; + } + HTTP2_FALLTHROUGH; + + case PayloadState::kReadPayload: + avail = state->AvailablePayload(db); + if (avail > 0) { + state->listener()->OnDataPayload(db->cursor(), avail); + db->AdvanceCursor(avail); + state->ConsumePayload(avail); + } + if (state->remaining_payload() > 0) { + payload_state_ = PayloadState::kReadPayload; + return DecodeStatus::kDecodeInProgress; + } + HTTP2_FALLTHROUGH; + + case PayloadState::kSkipPadding: + // SkipPadding handles the OnPadding callback. + if (state->SkipPadding(db)) { + state->listener()->OnDataEnd(); + return DecodeStatus::kDecodeDone; + } + payload_state_ = PayloadState::kSkipPadding; + return DecodeStatus::kDecodeInProgress; + } + HTTP2_BUG << "PayloadState: " << payload_state_; + return DecodeStatus::kDecodeError; +} + +} // namespace http2
diff --git a/http2/decoder/payload_decoders/data_payload_decoder.h b/http2/decoder/payload_decoders/data_payload_decoder.h new file mode 100644 index 0000000..1e083a9 --- /dev/null +++ b/http2/decoder/payload_decoders/data_payload_decoder.h
@@ -0,0 +1,54 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_DATA_PAYLOAD_DECODER_H_ +#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_DATA_PAYLOAD_DECODER_H_ + +// Decodes the payload of a DATA frame. + +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" + +namespace http2 { +namespace test { +class DataPayloadDecoderPeer; +} // namespace test + +class HTTP2_EXPORT_PRIVATE DataPayloadDecoder { + public: + // States during decoding of a DATA frame. + enum class PayloadState { + // The frame is padded and we need to read the PAD_LENGTH field (1 byte), + // and then call OnPadLength + kReadPadLength, + + // Report the non-padding portion of the payload to the listener's + // OnDataPayload method. + kReadPayload, + + // The decoder has finished with the non-padding portion of the payload, + // and is now ready to skip the trailing padding, if the frame has any. + kSkipPadding, + }; + + // Starts decoding a DATA frame's payload, and completes it if + // the entire payload is in the provided decode buffer. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a DATA frame's payload that has been split across + // decode buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); + + private: + friend class test::DataPayloadDecoderPeer; + + PayloadState payload_state_; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_DATA_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/data_payload_decoder_test.cc b/http2/decoder/payload_decoders/data_payload_decoder_test.cc new file mode 100644 index 0000000..e5253b9 --- /dev/null +++ b/http2/decoder/payload_decoders/data_payload_decoder_test.cc
@@ -0,0 +1,113 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/http2_structures_test_util.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +using ::testing::AssertionResult; + +namespace http2 { +namespace test { + +// Provides friend access to an instance of the payload decoder, and also +// provides info to aid in testing. +class DataPayloadDecoderPeer { + public: + static constexpr Http2FrameType FrameType() { return Http2FrameType::DATA; } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { + return Http2FrameFlag::PADDED; + } +}; + +namespace { + +struct Listener : public FramePartsCollector { + void OnDataStart(const Http2FrameHeader& header) override { + VLOG(1) << "OnDataStart: " << header; + StartFrame(header)->OnDataStart(header); + } + + void OnDataPayload(const char* data, size_t len) override { + VLOG(1) << "OnDataPayload: len=" << len; + CurrentFrame()->OnDataPayload(data, len); + } + + void OnDataEnd() override { + VLOG(1) << "OnDataEnd"; + EndFrame()->OnDataEnd(); + } + + void OnPadLength(size_t pad_length) override { + VLOG(1) << "OnPadLength: " << pad_length; + CurrentFrame()->OnPadLength(pad_length); + } + + void OnPadding(const char* padding, size_t skipped_length) override { + VLOG(1) << "OnPadding: " << skipped_length; + CurrentFrame()->OnPadding(padding, skipped_length); + } + + void OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) override { + VLOG(1) << "OnPaddingTooLong: " << header + << " missing_length: " << missing_length; + EndFrame()->OnPaddingTooLong(header, missing_length); + } +}; + +class DataPayloadDecoderTest + : public AbstractPaddablePayloadDecoderTest<DataPayloadDecoder, + DataPayloadDecoderPeer, + Listener> { + protected: + AssertionResult CreateAndDecodeDataOfSize(size_t data_size) { + Reset(); + uint8_t flags = RandFlags(); + + Http2String data_payload = Random().RandString(data_size); + frame_builder_.Append(data_payload); + MaybeAppendTrailingPadding(); + + Http2FrameHeader frame_header(frame_builder_.size(), Http2FrameType::DATA, + flags, RandStreamId()); + set_frame_header(frame_header); + ScrubFlagsOfHeader(&frame_header); + FrameParts expected(frame_header, data_payload, total_pad_length_); + VERIFY_AND_RETURN_SUCCESS( + DecodePayloadAndValidateSeveralWays(frame_builder_.buffer(), expected)); + } +}; + +INSTANTIATE_TEST_CASE_P(VariousPadLengths, + DataPayloadDecoderTest, + ::testing::Values(0, 1, 2, 3, 4, 254, 255, 256)); + +TEST_P(DataPayloadDecoderTest, VariousDataPayloadSizes) { + for (size_t data_size : {0, 1, 2, 3, 255, 256, 1024}) { + EXPECT_TRUE(CreateAndDecodeDataOfSize(data_size)); + } +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/decoder/payload_decoders/goaway_payload_decoder.cc b/http2/decoder/payload_decoders/goaway_payload_decoder.cc new file mode 100644 index 0000000..cf7b673 --- /dev/null +++ b/http2/decoder/payload_decoders/goaway_payload_decoder.cc
@@ -0,0 +1,121 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h" + +namespace http2 { + +std::ostream& operator<<(std::ostream& out, + GoAwayPayloadDecoder::PayloadState v) { + switch (v) { + case GoAwayPayloadDecoder::PayloadState::kStartDecodingFixedFields: + return out << "kStartDecodingFixedFields"; + case GoAwayPayloadDecoder::PayloadState::kHandleFixedFieldsStatus: + return out << "kHandleFixedFieldsStatus"; + case GoAwayPayloadDecoder::PayloadState::kReadOpaqueData: + return out << "kReadOpaqueData"; + case GoAwayPayloadDecoder::PayloadState::kResumeDecodingFixedFields: + return out << "kResumeDecodingFixedFields"; + } + // Since the value doesn't come over the wire, only a programming bug should + // result in reaching this point. + int unknown = static_cast<int>(v); + HTTP2_BUG << "Invalid GoAwayPayloadDecoder::PayloadState: " << unknown; + return out << "GoAwayPayloadDecoder::PayloadState(" << unknown << ")"; +} + +DecodeStatus GoAwayPayloadDecoder::StartDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "GoAwayPayloadDecoder::StartDecodingPayload: " + << state->frame_header(); + DCHECK_EQ(Http2FrameType::GOAWAY, state->frame_header().type); + DCHECK_LE(db->Remaining(), state->frame_header().payload_length); + DCHECK_EQ(0, state->frame_header().flags); + + state->InitializeRemainders(); + payload_state_ = PayloadState::kStartDecodingFixedFields; + return ResumeDecodingPayload(state, db); +} + +DecodeStatus GoAwayPayloadDecoder::ResumeDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "GoAwayPayloadDecoder::ResumeDecodingPayload: remaining_payload=" + << state->remaining_payload() + << ", db->Remaining=" << db->Remaining(); + + const Http2FrameHeader& frame_header = state->frame_header(); + DCHECK_EQ(Http2FrameType::GOAWAY, frame_header.type); + DCHECK_LE(db->Remaining(), frame_header.payload_length); + DCHECK_NE(PayloadState::kHandleFixedFieldsStatus, payload_state_); + + // |status| has to be initialized to some value to avoid compiler error in + // case PayloadState::kHandleFixedFieldsStatus below, but value does not + // matter, see DCHECK_NE above. + DecodeStatus status = DecodeStatus::kDecodeError; + size_t avail; + while (true) { + DVLOG(2) << "GoAwayPayloadDecoder::ResumeDecodingPayload payload_state_=" + << payload_state_; + switch (payload_state_) { + case PayloadState::kStartDecodingFixedFields: + status = state->StartDecodingStructureInPayload(&goaway_fields_, db); + HTTP2_FALLTHROUGH; + + case PayloadState::kHandleFixedFieldsStatus: + if (status == DecodeStatus::kDecodeDone) { + state->listener()->OnGoAwayStart(frame_header, goaway_fields_); + } else { + // Not done decoding the structure. Either we've got more payload + // to decode, or we've run out because the payload is too short, + // in which case OnFrameSizeError will have already been called. + DCHECK((status == DecodeStatus::kDecodeInProgress && + state->remaining_payload() > 0) || + (status == DecodeStatus::kDecodeError && + state->remaining_payload() == 0)) + << "\n status=" << status + << "; remaining_payload=" << state->remaining_payload(); + payload_state_ = PayloadState::kResumeDecodingFixedFields; + return status; + } + HTTP2_FALLTHROUGH; + + case PayloadState::kReadOpaqueData: + // The opaque data is all the remains to be decoded, so anything left + // in the decode buffer is opaque data. + avail = db->Remaining(); + if (avail > 0) { + state->listener()->OnGoAwayOpaqueData(db->cursor(), avail); + db->AdvanceCursor(avail); + state->ConsumePayload(avail); + } + if (state->remaining_payload() > 0) { + payload_state_ = PayloadState::kReadOpaqueData; + return DecodeStatus::kDecodeInProgress; + } + state->listener()->OnGoAwayEnd(); + return DecodeStatus::kDecodeDone; + + case PayloadState::kResumeDecodingFixedFields: + status = state->ResumeDecodingStructureInPayload(&goaway_fields_, db); + payload_state_ = PayloadState::kHandleFixedFieldsStatus; + continue; + } + HTTP2_BUG << "PayloadState: " << payload_state_; + } +} + +} // namespace http2
diff --git a/http2/decoder/payload_decoders/goaway_payload_decoder.h b/http2/decoder/payload_decoders/goaway_payload_decoder.h new file mode 100644 index 0000000..7a50873 --- /dev/null +++ b/http2/decoder/payload_decoders/goaway_payload_decoder.h
@@ -0,0 +1,66 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_GOAWAY_PAYLOAD_DECODER_H_ +#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_GOAWAY_PAYLOAD_DECODER_H_ + +// Decodes the payload of a GOAWAY frame. + +// TODO(jamessynge): Sweep through all payload decoders, changing the names of +// the PayloadState enums so that they are really states, and not actions. + +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" + +namespace http2 { +namespace test { +class GoAwayPayloadDecoderPeer; +} // namespace test + +class HTTP2_EXPORT_PRIVATE GoAwayPayloadDecoder { + public: + // States during decoding of a GOAWAY frame. + enum class PayloadState { + // At the start of the GOAWAY frame payload, ready to start decoding the + // fixed size fields into goaway_fields_. + kStartDecodingFixedFields, + + // Handle the DecodeStatus returned from starting or resuming the + // decoding of Http2GoAwayFields into goaway_fields_. If complete, + // calls OnGoAwayStart. + kHandleFixedFieldsStatus, + + // Report the Opaque Data portion of the payload to the listener's + // OnGoAwayOpaqueData method, and call OnGoAwayEnd when the end of the + // payload is reached. + kReadOpaqueData, + + // The fixed size fields weren't all available when the decoder first + // tried to decode them (state kStartDecodingFixedFields); this state + // resumes the decoding when ResumeDecodingPayload is called later. + kResumeDecodingFixedFields, + }; + + // Starts the decoding of a GOAWAY frame's payload, and completes it if + // the entire payload is in the provided decode buffer. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a GOAWAY frame's payload that has been split across + // decode buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); + + private: + friend class test::GoAwayPayloadDecoderPeer; + + Http2GoAwayFields goaway_fields_; + PayloadState payload_state_; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_GOAWAY_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/goaway_payload_decoder_test.cc b/http2/decoder/payload_decoders/goaway_payload_decoder_test.cc new file mode 100644 index 0000000..1df5214 --- /dev/null +++ b/http2/decoder/payload_decoders/goaway_payload_decoder_test.cc
@@ -0,0 +1,107 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures_test_util.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +namespace http2 { +namespace test { + +class GoAwayPayloadDecoderPeer { + public: + static constexpr Http2FrameType FrameType() { return Http2FrameType::GOAWAY; } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; } +}; + +namespace { + +struct Listener : public FramePartsCollector { + void OnGoAwayStart(const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) override { + VLOG(1) << "OnGoAwayStart header: " << header << "; goaway: " << goaway; + StartFrame(header)->OnGoAwayStart(header, goaway); + } + + void OnGoAwayOpaqueData(const char* data, size_t len) override { + VLOG(1) << "OnGoAwayOpaqueData: len=" << len; + CurrentFrame()->OnGoAwayOpaqueData(data, len); + } + + void OnGoAwayEnd() override { + VLOG(1) << "OnGoAwayEnd"; + EndFrame()->OnGoAwayEnd(); + } + + void OnFrameSizeError(const Http2FrameHeader& header) override { + VLOG(1) << "OnFrameSizeError: " << header; + FrameError(header)->OnFrameSizeError(header); + } +}; + +class GoAwayPayloadDecoderTest + : public AbstractPayloadDecoderTest<GoAwayPayloadDecoder, + GoAwayPayloadDecoderPeer, + Listener> {}; + +// Confirm we get an error if the payload is not long enough to hold +// Http2GoAwayFields. +TEST_F(GoAwayPayloadDecoderTest, Truncated) { + auto approve_size = [](size_t size) { + return size != Http2GoAwayFields::EncodedSize(); + }; + Http2FrameBuilder fb; + fb.Append(Http2GoAwayFields(123, Http2ErrorCode::ENHANCE_YOUR_CALM)); + EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size)); +} + +class GoAwayOpaqueDataLengthTests + : public GoAwayPayloadDecoderTest, + public ::testing::WithParamInterface<uint32_t> { + protected: + GoAwayOpaqueDataLengthTests() : length_(GetParam()) { + VLOG(1) << "################ length_=" << length_ << " ################"; + } + + const uint32_t length_; +}; + +INSTANTIATE_TEST_CASE_P(VariousLengths, + GoAwayOpaqueDataLengthTests, + ::testing::Values(0, 1, 2, 3, 4, 5, 6)); + +TEST_P(GoAwayOpaqueDataLengthTests, ValidLength) { + Http2GoAwayFields goaway; + Randomize(&goaway, RandomPtr()); + Http2String opaque_data = Random().RandString(length_); + Http2FrameBuilder fb; + fb.Append(goaway); + fb.Append(opaque_data); + Http2FrameHeader header(fb.size(), Http2FrameType::GOAWAY, RandFlags(), + RandStreamId()); + set_frame_header(header); + FrameParts expected(header, opaque_data); + expected.SetOptGoaway(goaway); + ASSERT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected)); +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/decoder/payload_decoders/headers_payload_decoder.cc b/http2/decoder/payload_decoders/headers_payload_decoder.cc new file mode 100644 index 0000000..ecaf0fa --- /dev/null +++ b/http2/decoder/payload_decoders/headers_payload_decoder.cc
@@ -0,0 +1,175 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h" + +namespace http2 { + +std::ostream& operator<<(std::ostream& out, + HeadersPayloadDecoder::PayloadState v) { + switch (v) { + case HeadersPayloadDecoder::PayloadState::kReadPadLength: + return out << "kReadPadLength"; + case HeadersPayloadDecoder::PayloadState::kStartDecodingPriorityFields: + return out << "kStartDecodingPriorityFields"; + case HeadersPayloadDecoder::PayloadState::kResumeDecodingPriorityFields: + return out << "kResumeDecodingPriorityFields"; + case HeadersPayloadDecoder::PayloadState::kReadPayload: + return out << "kReadPayload"; + case HeadersPayloadDecoder::PayloadState::kSkipPadding: + return out << "kSkipPadding"; + } + // Since the value doesn't come over the wire, only a programming bug should + // result in reaching this point. + int unknown = static_cast<int>(v); + HTTP2_BUG << "Invalid HeadersPayloadDecoder::PayloadState: " << unknown; + return out << "HeadersPayloadDecoder::PayloadState(" << unknown << ")"; +} + +DecodeStatus HeadersPayloadDecoder::StartDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + const Http2FrameHeader& frame_header = state->frame_header(); + const uint32_t total_length = frame_header.payload_length; + + DVLOG(2) << "HeadersPayloadDecoder::StartDecodingPayload: " << frame_header; + + DCHECK_EQ(Http2FrameType::HEADERS, frame_header.type); + DCHECK_LE(db->Remaining(), total_length); + DCHECK_EQ(0, frame_header.flags & + ~(Http2FrameFlag::END_STREAM | Http2FrameFlag::END_HEADERS | + Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY)); + + // Special case for HEADERS frames that contain only the HPACK block + // (fragment or whole) and that fit fully into the decode buffer. + // Why? Unencoded browser GET requests are typically under 1K and HPACK + // commonly shrinks request headers by 80%, so we can expect this to + // be common. + // TODO(jamessynge) Add counters here and to Spdy for determining how + // common this situation is. A possible approach is to create a + // Http2FrameDecoderListener that counts the callbacks and then forwards + // them on to another listener, which makes it easy to add and remove + // counting on a connection or even frame basis. + + // PADDED and PRIORITY both extra steps to decode, but if neither flag is + // set then we can decode faster. + const auto payload_flags = Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY; + if (!frame_header.HasAnyFlags(payload_flags)) { + DVLOG(2) << "StartDecodingPayload !IsPadded && !HasPriority"; + if (db->Remaining() == total_length) { + DVLOG(2) << "StartDecodingPayload all present"; + // Note that we don't cache the listener field so that the callee can + // replace it if the frame is bad. + // If this case is common enough, consider combining the 3 callbacks + // into one, especially if END_HEADERS is also set. + state->listener()->OnHeadersStart(frame_header); + if (total_length > 0) { + state->listener()->OnHpackFragment(db->cursor(), total_length); + db->AdvanceCursor(total_length); + } + state->listener()->OnHeadersEnd(); + return DecodeStatus::kDecodeDone; + } + payload_state_ = PayloadState::kReadPayload; + } else if (frame_header.IsPadded()) { + payload_state_ = PayloadState::kReadPadLength; + } else { + DCHECK(frame_header.HasPriority()) << frame_header; + payload_state_ = PayloadState::kStartDecodingPriorityFields; + } + state->InitializeRemainders(); + state->listener()->OnHeadersStart(frame_header); + return ResumeDecodingPayload(state, db); +} + +DecodeStatus HeadersPayloadDecoder::ResumeDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "HeadersPayloadDecoder::ResumeDecodingPayload " + << "remaining_payload=" << state->remaining_payload() + << "; db->Remaining=" << db->Remaining(); + + const Http2FrameHeader& frame_header = state->frame_header(); + + DCHECK_EQ(Http2FrameType::HEADERS, frame_header.type); + DCHECK_LE(state->remaining_payload_and_padding(), + frame_header.payload_length); + DCHECK_LE(db->Remaining(), state->remaining_payload_and_padding()); + DecodeStatus status; + size_t avail; + while (true) { + DVLOG(2) << "HeadersPayloadDecoder::ResumeDecodingPayload payload_state_=" + << payload_state_; + switch (payload_state_) { + case PayloadState::kReadPadLength: + // ReadPadLength handles the OnPadLength callback, and updating the + // remaining_payload and remaining_padding fields. If the amount of + // padding is too large to fit in the frame's payload, ReadPadLength + // instead calls OnPaddingTooLong and returns kDecodeError. + status = state->ReadPadLength(db, /*report_pad_length*/ true); + if (status != DecodeStatus::kDecodeDone) { + return status; + } + if (!frame_header.HasPriority()) { + payload_state_ = PayloadState::kReadPayload; + continue; + } + HTTP2_FALLTHROUGH; + + case PayloadState::kStartDecodingPriorityFields: + status = state->StartDecodingStructureInPayload(&priority_fields_, db); + if (status != DecodeStatus::kDecodeDone) { + payload_state_ = PayloadState::kResumeDecodingPriorityFields; + return status; + } + state->listener()->OnHeadersPriority(priority_fields_); + HTTP2_FALLTHROUGH; + + case PayloadState::kReadPayload: + avail = state->AvailablePayload(db); + if (avail > 0) { + state->listener()->OnHpackFragment(db->cursor(), avail); + db->AdvanceCursor(avail); + state->ConsumePayload(avail); + } + if (state->remaining_payload() > 0) { + payload_state_ = PayloadState::kReadPayload; + return DecodeStatus::kDecodeInProgress; + } + HTTP2_FALLTHROUGH; + + case PayloadState::kSkipPadding: + // SkipPadding handles the OnPadding callback. + if (state->SkipPadding(db)) { + state->listener()->OnHeadersEnd(); + return DecodeStatus::kDecodeDone; + } + payload_state_ = PayloadState::kSkipPadding; + return DecodeStatus::kDecodeInProgress; + + case PayloadState::kResumeDecodingPriorityFields: + status = state->ResumeDecodingStructureInPayload(&priority_fields_, db); + if (status != DecodeStatus::kDecodeDone) { + return status; + } + state->listener()->OnHeadersPriority(priority_fields_); + payload_state_ = PayloadState::kReadPayload; + continue; + } + HTTP2_BUG << "PayloadState: " << payload_state_; + } +} + +} // namespace http2
diff --git a/http2/decoder/payload_decoders/headers_payload_decoder.h b/http2/decoder/payload_decoders/headers_payload_decoder.h new file mode 100644 index 0000000..d3cdfe5 --- /dev/null +++ b/http2/decoder/payload_decoders/headers_payload_decoder.h
@@ -0,0 +1,67 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_HEADERS_PAYLOAD_DECODER_H_ +#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_HEADERS_PAYLOAD_DECODER_H_ + +// Decodes the payload of a HEADERS frame. + +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" + +namespace http2 { +namespace test { +class HeadersPayloadDecoderPeer; +} // namespace test + +class HTTP2_EXPORT_PRIVATE HeadersPayloadDecoder { + public: + // States during decoding of a HEADERS frame, unless the fast path kicks + // in, in which case the state machine will be bypassed. + enum class PayloadState { + // The PADDED flag is set, and we now need to read the Pad Length field + // (the first byte of the payload, after the common frame header). + kReadPadLength, + + // The PRIORITY flag is set, and we now need to read the fixed size priority + // fields (E, Stream Dependency, Weight) into priority_fields_. Calls on + // OnHeadersPriority if completely decodes those fields. + kStartDecodingPriorityFields, + + // The decoder passes the non-padding portion of the remaining payload + // (i.e. the HPACK block fragment) to the listener's OnHpackFragment method. + kReadPayload, + + // The decoder has finished with the HPACK block fragment, and is now + // ready to skip the trailing padding, if the frame has any. + kSkipPadding, + + // The fixed size fields weren't all available when the decoder first tried + // to decode them (state kStartDecodingPriorityFields); this state resumes + // the decoding when ResumeDecodingPayload is called later. + kResumeDecodingPriorityFields, + }; + + // Starts the decoding of a HEADERS frame's payload, and completes it if + // the entire payload is in the provided decode buffer. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a HEADERS frame's payload that has been split across + // decode buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); + + private: + friend class test::HeadersPayloadDecoderPeer; + + PayloadState payload_state_; + Http2PriorityFields priority_fields_; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_HEADERS_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/headers_payload_decoder_test.cc b/http2/decoder/payload_decoders/headers_payload_decoder_test.cc new file mode 100644 index 0000000..a6fcb65 --- /dev/null +++ b/http2/decoder/payload_decoders/headers_payload_decoder_test.cc
@@ -0,0 +1,158 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures_test_util.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +namespace http2 { +namespace test { + +class HeadersPayloadDecoderPeer { + public: + static constexpr Http2FrameType FrameType() { + return Http2FrameType::HEADERS; + } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { + return Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY; + } +}; + +namespace { + +// Listener handles all On* methods that are expected to be called. If any other +// On* methods of Http2FrameDecoderListener is called then the test fails; this +// is achieved by way of FailingHttp2FrameDecoderListener, the base class of +// FramePartsCollector. +// These On* methods make use of StartFrame, EndFrame, etc. of the base class +// to create and access to FrameParts instance(s) that will record the details. +// After decoding, the test validation code can access the FramePart instance(s) +// via the public methods of FramePartsCollector. +struct Listener : public FramePartsCollector { + void OnHeadersStart(const Http2FrameHeader& header) override { + VLOG(1) << "OnHeadersStart: " << header; + StartFrame(header)->OnHeadersStart(header); + } + + void OnHeadersPriority(const Http2PriorityFields& priority) override { + VLOG(1) << "OnHeadersPriority: " << priority; + CurrentFrame()->OnHeadersPriority(priority); + } + + void OnHpackFragment(const char* data, size_t len) override { + VLOG(1) << "OnHpackFragment: len=" << len; + CurrentFrame()->OnHpackFragment(data, len); + } + + void OnHeadersEnd() override { + VLOG(1) << "OnHeadersEnd"; + EndFrame()->OnHeadersEnd(); + } + + void OnPadLength(size_t pad_length) override { + VLOG(1) << "OnPadLength: " << pad_length; + CurrentFrame()->OnPadLength(pad_length); + } + + void OnPadding(const char* padding, size_t skipped_length) override { + VLOG(1) << "OnPadding: " << skipped_length; + CurrentFrame()->OnPadding(padding, skipped_length); + } + + void OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) override { + VLOG(1) << "OnPaddingTooLong: " << header + << "; missing_length: " << missing_length; + FrameError(header)->OnPaddingTooLong(header, missing_length); + } + + void OnFrameSizeError(const Http2FrameHeader& header) override { + VLOG(1) << "OnFrameSizeError: " << header; + FrameError(header)->OnFrameSizeError(header); + } +}; + +class HeadersPayloadDecoderTest + : public AbstractPaddablePayloadDecoderTest<HeadersPayloadDecoder, + HeadersPayloadDecoderPeer, + Listener> {}; + +INSTANTIATE_TEST_CASE_P(VariousPadLengths, + HeadersPayloadDecoderTest, + ::testing::Values(0, 1, 2, 3, 4, 254, 255, 256)); + +// Decode various sizes of (fake) HPACK payload, both with and without the +// PRIORITY flag set. +TEST_P(HeadersPayloadDecoderTest, VariousHpackPayloadSizes) { + for (size_t hpack_size : {0, 1, 2, 3, 255, 256, 1024}) { + LOG(INFO) << "########### hpack_size = " << hpack_size << " ###########"; + Http2PriorityFields priority(RandStreamId(), 1 + Random().Rand8(), + Random().OneIn(2)); + + for (bool has_priority : {false, true}) { + Reset(); + ASSERT_EQ(IsPadded() ? 1u : 0u, frame_builder_.size()); + uint8_t flags = RandFlags(); + if (has_priority) { + flags |= Http2FrameFlag::PRIORITY; + frame_builder_.Append(priority); + } + + Http2String hpack_payload = Random().RandString(hpack_size); + frame_builder_.Append(hpack_payload); + + MaybeAppendTrailingPadding(); + Http2FrameHeader frame_header(frame_builder_.size(), + Http2FrameType::HEADERS, flags, + RandStreamId()); + set_frame_header(frame_header); + ScrubFlagsOfHeader(&frame_header); + FrameParts expected(frame_header, hpack_payload, total_pad_length_); + if (has_priority) { + expected.SetOptPriority(priority); + } + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(frame_builder_.buffer(), + expected)); + } + } +} + +// Confirm we get an error if the PRIORITY flag is set but the payload is +// not long enough, regardless of the amount of (valid) padding. +TEST_P(HeadersPayloadDecoderTest, Truncated) { + auto approve_size = [](size_t size) { + return size != Http2PriorityFields::EncodedSize(); + }; + Http2FrameBuilder fb; + fb.Append(Http2PriorityFields(RandStreamId(), 1 + Random().Rand8(), + Random().OneIn(2))); + EXPECT_TRUE(VerifyDetectsMultipleFrameSizeErrors( + Http2FrameFlag::PRIORITY, fb.buffer(), approve_size, total_pad_length_)); +} + +// Confirm we get an error if the PADDED flag is set but the payload is not +// long enough to hold even the Pad Length amount of padding. +TEST_P(HeadersPayloadDecoderTest, PaddingTooLong) { + EXPECT_TRUE(VerifyDetectsPaddingTooLong()); +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/decoder/payload_decoders/payload_decoder_base_test_util.cc b/http2/decoder/payload_decoders/payload_decoder_base_test_util.cc new file mode 100644 index 0000000..88e1b98 --- /dev/null +++ b/http2/decoder/payload_decoders/payload_decoder_base_test_util.cc
@@ -0,0 +1,97 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state_test_util.h" +#include "net/third_party/quiche/src/http2/http2_structures_test_util.h" + +namespace http2 { +namespace test { +PayloadDecoderBaseTest::PayloadDecoderBaseTest() { + // If the test adds more data after the frame payload, + // stop as soon as the payload is decoded. + stop_decode_on_done_ = true; + frame_header_is_set_ = false; + Randomize(&frame_header_, RandomPtr()); +} + +DecodeStatus PayloadDecoderBaseTest::StartDecoding(DecodeBuffer* db) { + DVLOG(2) << "StartDecoding, db->Remaining=" << db->Remaining(); + // Make sure sub-class has set frame_header_ so that we can inject it + // into the payload decoder below. + if (!frame_header_is_set_) { + ADD_FAILURE() << "frame_header_ is not set"; + return DecodeStatus::kDecodeError; + } + // The contract with the payload decoders is that they won't receive a + // decode buffer that extends beyond the end of the frame. + if (db->Remaining() > frame_header_.payload_length) { + ADD_FAILURE() << "DecodeBuffer has too much data: " << db->Remaining() + << " > " << frame_header_.payload_length; + return DecodeStatus::kDecodeError; + } + + // Prepare the payload decoder. + PreparePayloadDecoder(); + + // Reconstruct the FrameDecoderState, prepare the listener, and add it to + // the FrameDecoderState. + Http2DefaultReconstructObject(&frame_decoder_state_, RandomPtr()); + frame_decoder_state_.set_listener(PrepareListener()); + + // Make sure that a listener was provided. + if (frame_decoder_state_.listener() == nullptr) { + ADD_FAILURE() << "PrepareListener must return a listener."; + return DecodeStatus::kDecodeError; + } + + // Now that nothing in the payload decoder should be valid, inject the + // Http2FrameHeader whose payload we're about to decode. That header is the + // only state that a payload decoder should expect is valid when its Start + // method is called. + FrameDecoderStatePeer::set_frame_header(frame_header_, &frame_decoder_state_); + DecodeStatus status = StartDecodingPayload(db); + if (status != DecodeStatus::kDecodeInProgress) { + // Keep track of this so that a concrete test can verify that both fast + // and slow decoding paths have been tested. + ++fast_decode_count_; + } + return status; +} + +DecodeStatus PayloadDecoderBaseTest::ResumeDecoding(DecodeBuffer* db) { + DVLOG(2) << "ResumeDecoding, db->Remaining=" << db->Remaining(); + DecodeStatus status = ResumeDecodingPayload(db); + if (status != DecodeStatus::kDecodeInProgress) { + // Keep track of this so that a concrete test can verify that both fast + // and slow decoding paths have been tested. + ++slow_decode_count_; + } + return status; +} + +::testing::AssertionResult +PayloadDecoderBaseTest::DecodePayloadAndValidateSeveralWays( + Http2StringPiece payload, + Validator validator) { + VERIFY_TRUE(frame_header_is_set_); + // Cap the payload to be decoded at the declared payload length. This is + // required by the decoders' preconditions; they are designed on the + // assumption that they're never passed more than they're permitted to + // consume. + // Note that it is OK if the payload is too short; the validator may be + // designed to check for that. + if (payload.size() > frame_header_.payload_length) { + payload = Http2StringPiece(payload.data(), frame_header_.payload_length); + } + DecodeBuffer db(payload); + ResetDecodeSpeedCounters(); + const bool kMayReturnZeroOnFirst = false; + return DecodeAndValidateSeveralWays(&db, kMayReturnZeroOnFirst, validator); +} + +} // namespace test +} // namespace http2
diff --git a/http2/decoder/payload_decoders/payload_decoder_base_test_util.h b/http2/decoder/payload_decoders/payload_decoder_base_test_util.h new file mode 100644 index 0000000..8297e70 --- /dev/null +++ b/http2/decoder/payload_decoders/payload_decoder_base_test_util.h
@@ -0,0 +1,455 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_ +#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_ + +// Base class for testing concrete payload decoder classes. + +#include <stddef.h> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_constants_test_util.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_reconstruct_object.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h" +#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +namespace http2 { +namespace test { + +// Base class for tests of payload decoders. Below this there is a templated +// sub-class that adds a bunch of type specific features. +class PayloadDecoderBaseTest : public RandomDecoderTest { + protected: + PayloadDecoderBaseTest(); + + // Virtual functions to be implemented by the test classes for the individual + // payload decoders... + + // Start decoding the payload. + virtual DecodeStatus StartDecodingPayload(DecodeBuffer* db) = 0; + + // Resume decoding the payload. + virtual DecodeStatus ResumeDecodingPayload(DecodeBuffer* db) = 0; + + // In support of ensuring that we're really accessing and updating the + // decoder, prepare the decoder by, for example, overwriting the decoder. + virtual void PreparePayloadDecoder() = 0; + + // Get the listener to be inserted into the FrameDecoderState, ready for + // listening (e.g. reset if it is a FramePartsCollector). + virtual Http2FrameDecoderListener* PrepareListener() = 0; + + // Record a frame header for use on each call to StartDecoding. + void set_frame_header(const Http2FrameHeader& header) { + EXPECT_EQ(0, InvalidFlagMaskForFrameType(header.type) & header.flags); + if (!frame_header_is_set_ || frame_header_ != header) { + VLOG(2) << "set_frame_header: " << frame_header_; + } + frame_header_ = header; + frame_header_is_set_ = true; + } + + FrameDecoderState* mutable_state() { return &frame_decoder_state_; } + + // Randomize the payload decoder, sets the payload decoder's frame_header_, + // then start decoding the payload. Called by RandomDecoderTest. This method + // is final so that we can always perform certain actions when + // RandomDecoderTest starts the decoding of a payload, such as randomizing the + // the payload decoder, injecting the frame header and counting fast decoding + // cases. Sub-classes must implement StartDecodingPayload to perform their + // initial decoding of a frame's payload. + DecodeStatus StartDecoding(DecodeBuffer* db) final; + + // Called by RandomDecoderTest. This method is final so that we can always + // perform certain actions when RandomDecoderTest calls it, such as counting + // slow decode cases. Sub-classes must implement ResumeDecodingPayload to + // continue decoding the frame's payload, which must not all be in one buffer. + DecodeStatus ResumeDecoding(DecodeBuffer* db) final; + + // Given the specified payload (without the common frame header), decode + // it with several partitionings of the payload. + ::testing::AssertionResult DecodePayloadAndValidateSeveralWays( + Http2StringPiece payload, + Validator validator); + + // TODO(jamessynge): Add helper method for verifying these are both non-zero, + // and call the new method from tests that expect successful decoding. + void ResetDecodeSpeedCounters() { + fast_decode_count_ = 0; + slow_decode_count_ = 0; + } + + // Count of payloads that are full decoded by StartDecodingPayload, or that + // an error was detected by StartDecodingPayload. + size_t fast_decode_count_ = 0; + + // Count of payloads that require calling ResumeDecodingPayload in order to + // decode them completely (or to detect an error during decoding). + size_t slow_decode_count_ = 0; + + private: + bool frame_header_is_set_ = false; + Http2FrameHeader frame_header_; + FrameDecoderState frame_decoder_state_; +}; + +// Base class for payload decoders of type Decoder, with corresponding test +// peer of type DecoderPeer, and using class Listener as the implementation +// of Http2FrameDecoderListenerInterface to be used during decoding. +// Typically Listener is a sub-class of FramePartsCollector. +// SupportedFrameType is set to false only for UnknownPayloadDecoder. +template <class Decoder, + class DecoderPeer, + class Listener, + bool SupportedFrameType = true> +class AbstractPayloadDecoderTest : public PayloadDecoderBaseTest { + protected: + // An ApproveSize function returns true to approve decoding the specified + // size of payload, else false to skip that size. Typically used for negative + // tests; for example, decoding a SETTINGS frame at all sizes except for + // multiples of 6. + typedef std::function<bool(size_t size)> ApproveSize; + + AbstractPayloadDecoderTest() {} + + // These tests are in setup rather than the constructor for two reasons: + // 1) Constructors are not allowed to fail, so gUnit documents that EXPECT_* + // and ASSERT_* are not allowed in constructors, and should instead be in + // SetUp if they are needed before the body of the test is executed. + // 2) To allow the sub-class constructor to make any desired modifications to + // the DecoderPeer before these tests are executed; in particular, + // UnknownPayloadDecoderPeer has not got a fixed frame type, but it is + // instead set during the test's constructor. + void SetUp() override { + PayloadDecoderBaseTest::SetUp(); + + // Confirm that DecoderPeer et al returns sensible values. Using auto as the + // variable type so that no (narrowing) conversions take place that hide + // problems; i.e. if someone changes KnownFlagsMaskForFrameType so that it + // doesn't return a uint8, and has bits above the low-order 8 bits set, this + // bit of paranoia should detect the problem before we get too far. + auto frame_type = DecoderPeer::FrameType(); + if (SupportedFrameType) { + EXPECT_TRUE(IsSupportedHttp2FrameType(frame_type)) << frame_type; + } else { + EXPECT_FALSE(IsSupportedHttp2FrameType(frame_type)) << frame_type; + } + + auto known_flags = KnownFlagsMaskForFrameType(frame_type); + EXPECT_EQ(known_flags, known_flags & 0xff); + + auto flags_to_avoid = DecoderPeer::FlagsAffectingPayloadDecoding(); + EXPECT_EQ(flags_to_avoid, flags_to_avoid & known_flags); + } + + void PreparePayloadDecoder() override { + Http2DefaultReconstructObject(&payload_decoder_, RandomPtr()); + } + + Http2FrameDecoderListener* PrepareListener() override { + listener_.Reset(); + return &listener_; + } + + // Returns random flags, but only those valid for the frame type, yet not + // those that the DecoderPeer says will affect the decoding of the payload + // (e.g. the PRIORTY flag on a HEADERS frame or PADDED on DATA frames). + uint8_t RandFlags() { + return Random().Rand8() & + KnownFlagsMaskForFrameType(DecoderPeer::FrameType()) & + ~DecoderPeer::FlagsAffectingPayloadDecoding(); + } + + // Start decoding the payload. + DecodeStatus StartDecodingPayload(DecodeBuffer* db) override { + DVLOG(2) << "StartDecodingPayload, db->Remaining=" << db->Remaining(); + return payload_decoder_.StartDecodingPayload(mutable_state(), db); + } + + // Resume decoding the payload. + DecodeStatus ResumeDecodingPayload(DecodeBuffer* db) override { + DVLOG(2) << "ResumeDecodingPayload, db->Remaining=" << db->Remaining(); + return payload_decoder_.ResumeDecodingPayload(mutable_state(), db); + } + + // Decode one frame's payload and confirm that the listener recorded the + // expected FrameParts instance, and only FrameParts instance. The payload + // will be decoded several times with different partitionings of the payload, + // and after each the validator will be called. + AssertionResult DecodePayloadAndValidateSeveralWays( + Http2StringPiece payload, + const FrameParts& expected) { + auto validator = [&expected, this]() -> AssertionResult { + VERIFY_FALSE(listener_.IsInProgress()); + VERIFY_EQ(1u, listener_.size()); + VERIFY_AND_RETURN_SUCCESS(expected.VerifyEquals(*listener_.frame(0))); + }; + return PayloadDecoderBaseTest::DecodePayloadAndValidateSeveralWays( + payload, ValidateDoneAndEmpty(validator)); + } + + // Decode one frame's payload, expecting that the final status will be + // kDecodeError, and that OnFrameSizeError will have been called on the + // listener. The payload will be decoded several times with different + // partitionings of the payload. The type WrappedValidator is either + // RandomDecoderTest::Validator, RandomDecoderTest::NoArgValidator or + // std::nullptr_t (not extra validation). + template <typename WrappedValidator> + ::testing::AssertionResult VerifyDetectsFrameSizeError( + Http2StringPiece payload, + const Http2FrameHeader& header, + WrappedValidator wrapped_validator) { + set_frame_header(header); + // If wrapped_validator is not a RandomDecoderTest::Validator, make it so. + Validator validator = ToValidator(wrapped_validator); + // And wrap that validator in another which will check that we've reached + // the expected state of kDecodeError with OnFrameSizeError having been + // called by the payload decoder. + validator = [header, validator, this]( + const DecodeBuffer& input, + DecodeStatus status) -> ::testing::AssertionResult { + DVLOG(2) << "VerifyDetectsFrameSizeError validator; status=" << status + << "; input.Remaining=" << input.Remaining(); + VERIFY_EQ(DecodeStatus::kDecodeError, status); + VERIFY_FALSE(listener_.IsInProgress()); + VERIFY_EQ(1u, listener_.size()); + const FrameParts* frame = listener_.frame(0); + VERIFY_EQ(header, frame->GetFrameHeader()); + VERIFY_TRUE(frame->GetHasFrameSizeError()); + // Verify did not get OnPaddingTooLong, as we should only ever produce + // one of these two errors for a single frame. + VERIFY_FALSE(frame->GetOptMissingLength()); + return validator(input, status); + }; + VERIFY_AND_RETURN_SUCCESS( + PayloadDecoderBaseTest::DecodePayloadAndValidateSeveralWays(payload, + validator)); + } + + // Confirm that we get OnFrameSizeError when trying to decode unpadded_payload + // at all sizes from zero to unpadded_payload.size(), except those sizes not + // approved by approve_size. + // If total_pad_length is greater than zero, then that amount of padding + // is added to the payload (including the Pad Length field). + // The flags will be required_flags, PADDED if total_pad_length > 0, and some + // randomly selected flag bits not excluded by FlagsAffectingPayloadDecoding. + ::testing::AssertionResult VerifyDetectsMultipleFrameSizeErrors( + uint8_t required_flags, + Http2StringPiece unpadded_payload, + ApproveSize approve_size, + int total_pad_length) { + // required_flags should come from those that are defined for the frame + // type AND are those that affect the decoding of the payload (otherwise, + // the flag shouldn't be required). + Http2FrameType frame_type = DecoderPeer::FrameType(); + VERIFY_EQ(required_flags, + required_flags & KnownFlagsMaskForFrameType(frame_type)); + VERIFY_EQ(required_flags, + required_flags & DecoderPeer::FlagsAffectingPayloadDecoding()); + + if (0 != + (Http2FrameFlag::PADDED & KnownFlagsMaskForFrameType(frame_type))) { + // Frame type supports padding. + if (total_pad_length == 0) { + required_flags &= ~Http2FrameFlag::PADDED; + } else { + required_flags |= Http2FrameFlag::PADDED; + } + } else { + VERIFY_EQ(0, total_pad_length); + } + + bool validated = false; + for (size_t real_payload_size = 0; + real_payload_size <= unpadded_payload.size(); ++real_payload_size) { + if (approve_size != nullptr && !approve_size(real_payload_size)) { + continue; + } + VLOG(1) << "real_payload_size=" << real_payload_size; + uint8_t flags = required_flags | RandFlags(); + Http2FrameBuilder fb; + if (total_pad_length > 0) { + // total_pad_length_ includes the size of the Pad Length field, and thus + // ranges from 0 (no PADDED flag) to 256 (Pad Length == 255). + fb.AppendUInt8(total_pad_length - 1); + } + // Append a subset of the unpadded_payload, which the decoder should + // determine is not a valid amount. + fb.Append(unpadded_payload.substr(0, real_payload_size)); + if (total_pad_length > 0) { + fb.AppendZeroes(total_pad_length - 1); + } + // We choose a random stream id because the payload decoders aren't + // checking stream ids. + uint32_t stream_id = RandStreamId(); + Http2FrameHeader header(fb.size(), frame_type, flags, stream_id); + VERIFY_SUCCESS(VerifyDetectsFrameSizeError(fb.buffer(), header, nullptr)); + validated = true; + } + VERIFY_TRUE(validated); + return ::testing::AssertionSuccess(); + } + + // As above, but for frames without padding. + ::testing::AssertionResult VerifyDetectsFrameSizeError( + uint8_t required_flags, + Http2StringPiece unpadded_payload, + const ApproveSize& approve_size) { + Http2FrameType frame_type = DecoderPeer::FrameType(); + uint8_t known_flags = KnownFlagsMaskForFrameType(frame_type); + VERIFY_EQ(0, known_flags & Http2FrameFlag::PADDED); + VERIFY_EQ(0, required_flags & Http2FrameFlag::PADDED); + VERIFY_AND_RETURN_SUCCESS(VerifyDetectsMultipleFrameSizeErrors( + required_flags, unpadded_payload, approve_size, 0)); + } + + Listener listener_; + union { + // Confirm at compile time that Decoder can be in an anonymous union, + // i.e. complain loudly if Decoder has members that prevent this, as it + // becomes annoying and possibly difficult to deal with non-anonymous + // unions and such union members. + Decoder payload_decoder_; + }; +}; + +// A base class for tests parameterized by the total number of bytes of +// padding, including the Pad Length field (i.e. a total_pad_length of 0 +// means unpadded as there is then no room for the Pad Length field). +// The frame type must support padding. +template <class Decoder, class DecoderPeer, class Listener> +class AbstractPaddablePayloadDecoderTest + : public AbstractPayloadDecoderTest<Decoder, DecoderPeer, Listener>, + public ::testing::WithParamInterface<int> { + typedef AbstractPayloadDecoderTest<Decoder, DecoderPeer, Listener> Base; + + protected: + using Base::listener_; + using Base::Random; + using Base::RandStreamId; + using Base::set_frame_header; + typedef typename Base::Validator Validator; + + AbstractPaddablePayloadDecoderTest() : total_pad_length_(GetParam()) { + LOG(INFO) << "total_pad_length_ = " << total_pad_length_; + } + + // Note that total_pad_length_ includes the size of the Pad Length field, + // and thus ranges from 0 (no PADDED flag) to 256 (Pad Length == 255). + bool IsPadded() const { return total_pad_length_ > 0; } + + // Value of the Pad Length field. Only call if IsPadded. + size_t pad_length() const { + EXPECT_TRUE(IsPadded()); + return total_pad_length_ - 1; + } + + // Clear the frame builder and add the Pad Length field if appropriate. + void Reset() { + frame_builder_ = Http2FrameBuilder(); + if (IsPadded()) { + frame_builder_.AppendUInt8(pad_length()); + } + } + + void MaybeAppendTrailingPadding() { + if (IsPadded()) { + frame_builder_.AppendZeroes(pad_length()); + } + } + + uint8_t RandFlags() { + uint8_t flags = Base::RandFlags(); + if (IsPadded()) { + flags |= Http2FrameFlag::PADDED; + } else { + flags &= ~Http2FrameFlag::PADDED; + } + return flags; + } + + // Verify that we get OnPaddingTooLong when decoding payload, and that the + // amount of missing padding is as specified. header.IsPadded must be true, + // and the payload must be empty or the PadLength field must be too large. + ::testing::AssertionResult VerifyDetectsPaddingTooLong( + Http2StringPiece payload, + const Http2FrameHeader& header, + size_t expected_missing_length) { + set_frame_header(header); + auto& listener = listener_; + Validator validator = + [header, expected_missing_length, &listener]( + const DecodeBuffer& input, + DecodeStatus status) -> ::testing::AssertionResult { + VERIFY_EQ(DecodeStatus::kDecodeError, status); + VERIFY_FALSE(listener.IsInProgress()); + VERIFY_EQ(1u, listener.size()); + const FrameParts* frame = listener.frame(0); + VERIFY_EQ(header, frame->GetFrameHeader()); + VERIFY_TRUE(frame->GetOptMissingLength()); + VERIFY_EQ(expected_missing_length, frame->GetOptMissingLength().value()); + // Verify did not get OnFrameSizeError. + VERIFY_FALSE(frame->GetHasFrameSizeError()); + return ::testing::AssertionSuccess(); + }; + VERIFY_AND_RETURN_SUCCESS( + PayloadDecoderBaseTest::DecodePayloadAndValidateSeveralWays(payload, + validator)); + } + + // Verifies that we get OnPaddingTooLong for a padded frame payload whose + // (randomly selected) payload length is less than total_pad_length_. + // Flags will be selected at random, except PADDED will be set and + // flags_to_avoid will not be set. The stream id is selected at random. + ::testing::AssertionResult VerifyDetectsPaddingTooLong() { + uint8_t flags = RandFlags() | Http2FrameFlag::PADDED; + + // Create an all padding payload for total_pad_length_. + int payload_length = 0; + Http2FrameBuilder fb; + if (IsPadded()) { + fb.AppendUInt8(pad_length()); + fb.AppendZeroes(pad_length()); + VLOG(1) << "fb.size=" << fb.size(); + // Pick a random length for the payload that is shorter than neccesary. + payload_length = Random().Uniform(fb.size()); + } + + VLOG(1) << "payload_length=" << payload_length; + Http2String payload = fb.buffer().substr(0, payload_length); + + // The missing length is the amount we cut off the end, unless + // payload_length is zero, in which case the decoder knows only that 1 + // byte, the Pad Length field, is missing. + size_t missing_length = + payload_length == 0 ? 1 : fb.size() - payload_length; + VLOG(1) << "missing_length=" << missing_length; + + const Http2FrameHeader header(payload_length, DecoderPeer::FrameType(), + flags, RandStreamId()); + VERIFY_AND_RETURN_SUCCESS( + VerifyDetectsPaddingTooLong(payload, header, missing_length)); + } + + // total_pad_length_ includes the size of the Pad Length field, and thus + // ranges from 0 (no PADDED flag) to 256 (Pad Length == 255). + const size_t total_pad_length_; + Http2FrameBuilder frame_builder_; +}; + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_
diff --git a/http2/decoder/payload_decoders/ping_payload_decoder.cc b/http2/decoder/payload_decoders/ping_payload_decoder.cc new file mode 100644 index 0000000..8d98046 --- /dev/null +++ b/http2/decoder/payload_decoders/ping_payload_decoder.cc
@@ -0,0 +1,89 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" + +namespace http2 { +namespace { +constexpr auto kOpaqueSize = Http2PingFields::EncodedSize(); +} + +DecodeStatus PingPayloadDecoder::StartDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db) { + const Http2FrameHeader& frame_header = state->frame_header(); + const uint32_t total_length = frame_header.payload_length; + + DVLOG(2) << "PingPayloadDecoder::StartDecodingPayload: " << frame_header; + DCHECK_EQ(Http2FrameType::PING, frame_header.type); + DCHECK_LE(db->Remaining(), total_length); + DCHECK_EQ(0, frame_header.flags & ~(Http2FrameFlag::ACK)); + + // Is the payload entirely in the decode buffer and is it the correct size? + // Given the size of the header and payload (17 bytes total), this is most + // likely the case the vast majority of the time. + if (db->Remaining() == kOpaqueSize && total_length == kOpaqueSize) { + // Special case this situation as it allows us to avoid any copying; + // the other path makes two copies, first into the buffer in + // Http2StructureDecoder as it accumulates the 8 bytes of opaque data, + // and a second copy into the Http2PingFields member of in this class. + // This supports the claim that this decoder is (mostly) non-buffering. + static_assert(sizeof(Http2PingFields) == kOpaqueSize, + "If not, then can't enter this block!"); + auto* ping = reinterpret_cast<const Http2PingFields*>(db->cursor()); + if (frame_header.IsAck()) { + state->listener()->OnPingAck(frame_header, *ping); + } else { + state->listener()->OnPing(frame_header, *ping); + } + db->AdvanceCursor(kOpaqueSize); + return DecodeStatus::kDecodeDone; + } + state->InitializeRemainders(); + return HandleStatus( + state, state->StartDecodingStructureInPayload(&ping_fields_, db)); +} + +DecodeStatus PingPayloadDecoder::ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "ResumeDecodingPayload: remaining_payload=" + << state->remaining_payload(); + DCHECK_EQ(Http2FrameType::PING, state->frame_header().type); + DCHECK_LE(db->Remaining(), state->frame_header().payload_length); + return HandleStatus( + state, state->ResumeDecodingStructureInPayload(&ping_fields_, db)); +} + +DecodeStatus PingPayloadDecoder::HandleStatus(FrameDecoderState* state, + DecodeStatus status) { + DVLOG(2) << "HandleStatus: status=" << status + << "; remaining_payload=" << state->remaining_payload(); + if (status == DecodeStatus::kDecodeDone) { + if (state->remaining_payload() == 0) { + const Http2FrameHeader& frame_header = state->frame_header(); + if (frame_header.IsAck()) { + state->listener()->OnPingAck(frame_header, ping_fields_); + } else { + state->listener()->OnPing(frame_header, ping_fields_); + } + return DecodeStatus::kDecodeDone; + } + // Payload is too long. + return state->ReportFrameSizeError(); + } + // Not done decoding the structure. Either we've got more payload to decode, + // or we've run out because the payload is too short. + DCHECK( + (status == DecodeStatus::kDecodeInProgress && + state->remaining_payload() > 0) || + (status == DecodeStatus::kDecodeError && state->remaining_payload() == 0)) + << "\n status=" << status + << "; remaining_payload=" << state->remaining_payload(); + return status; +} + +} // namespace http2
diff --git a/http2/decoder/payload_decoders/ping_payload_decoder.h b/http2/decoder/payload_decoders/ping_payload_decoder.h new file mode 100644 index 0000000..84704fb --- /dev/null +++ b/http2/decoder/payload_decoders/ping_payload_decoder.h
@@ -0,0 +1,43 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PING_PAYLOAD_DECODER_H_ +#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PING_PAYLOAD_DECODER_H_ + +// Decodes the payload of a PING frame; for the RFC, see: +// http://httpwg.org/specs/rfc7540.html#PING + +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" + +namespace http2 { +namespace test { +class PingPayloadDecoderPeer; +} // namespace test + +class HTTP2_EXPORT_PRIVATE PingPayloadDecoder { + public: + // Starts the decoding of a PING frame's payload, and completes it if the + // entire payload is in the provided decode buffer. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a PING frame's payload that has been split across + // decode buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); + + private: + friend class test::PingPayloadDecoderPeer; + + DecodeStatus HandleStatus(FrameDecoderState* state, DecodeStatus status); + + Http2PingFields ping_fields_; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PING_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/ping_payload_decoder_test.cc b/http2/decoder/payload_decoders/ping_payload_decoder_test.cc new file mode 100644 index 0000000..34833b2 --- /dev/null +++ b/http2/decoder/payload_decoders/ping_payload_decoder_test.cc
@@ -0,0 +1,110 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures_test_util.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +namespace http2 { +namespace test { + +class PingPayloadDecoderPeer { + public: + static constexpr Http2FrameType FrameType() { return Http2FrameType::PING; } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; } +}; + +namespace { + +struct Listener : public FramePartsCollector { + void OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) override { + VLOG(1) << "OnPing: " << header << "; " << ping; + StartAndEndFrame(header)->OnPing(header, ping); + } + + void OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) override { + VLOG(1) << "OnPingAck: " << header << "; " << ping; + StartAndEndFrame(header)->OnPingAck(header, ping); + } + + void OnFrameSizeError(const Http2FrameHeader& header) override { + VLOG(1) << "OnFrameSizeError: " << header; + FrameError(header)->OnFrameSizeError(header); + } +}; + +class PingPayloadDecoderTest + : public AbstractPayloadDecoderTest<PingPayloadDecoder, + PingPayloadDecoderPeer, + Listener> { + protected: + Http2PingFields RandPingFields() { + Http2PingFields fields; + test::Randomize(&fields, RandomPtr()); + return fields; + } +}; + +// Confirm we get an error if the payload is not the correct size to hold +// exactly one Http2PingFields. +TEST_F(PingPayloadDecoderTest, WrongSize) { + auto approve_size = [](size_t size) { + return size != Http2PingFields::EncodedSize(); + }; + Http2FrameBuilder fb; + fb.Append(RandPingFields()); + fb.Append(RandPingFields()); + fb.Append(RandPingFields()); + EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size)); +} + +TEST_F(PingPayloadDecoderTest, Ping) { + for (int n = 0; n < 100; ++n) { + Http2PingFields fields = RandPingFields(); + Http2FrameBuilder fb; + fb.Append(fields); + Http2FrameHeader header(fb.size(), Http2FrameType::PING, + RandFlags() & ~Http2FrameFlag::ACK, RandStreamId()); + set_frame_header(header); + FrameParts expected(header); + expected.SetOptPing(fields); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected)); + } +} + +TEST_F(PingPayloadDecoderTest, PingAck) { + for (int n = 0; n < 100; ++n) { + Http2PingFields fields; + Randomize(&fields, RandomPtr()); + Http2FrameBuilder fb; + fb.Append(fields); + Http2FrameHeader header(fb.size(), Http2FrameType::PING, + RandFlags() | Http2FrameFlag::ACK, RandStreamId()); + set_frame_header(header); + FrameParts expected(header); + expected.SetOptPing(fields); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected)); + } +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/decoder/payload_decoders/priority_payload_decoder.cc b/http2/decoder/payload_decoders/priority_payload_decoder.cc new file mode 100644 index 0000000..7be1c95 --- /dev/null +++ b/http2/decoder/payload_decoders/priority_payload_decoder.cc
@@ -0,0 +1,64 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" + +namespace http2 { + +DecodeStatus PriorityPayloadDecoder::StartDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "PriorityPayloadDecoder::StartDecodingPayload: " + << state->frame_header(); + DCHECK_EQ(Http2FrameType::PRIORITY, state->frame_header().type); + DCHECK_LE(db->Remaining(), state->frame_header().payload_length); + // PRIORITY frames have no flags. + DCHECK_EQ(0, state->frame_header().flags); + state->InitializeRemainders(); + return HandleStatus( + state, state->StartDecodingStructureInPayload(&priority_fields_, db)); +} + +DecodeStatus PriorityPayloadDecoder::ResumeDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "PriorityPayloadDecoder::ResumeDecodingPayload" + << " remaining_payload=" << state->remaining_payload() + << " db->Remaining=" << db->Remaining(); + DCHECK_EQ(Http2FrameType::PRIORITY, state->frame_header().type); + DCHECK_LE(db->Remaining(), state->frame_header().payload_length); + return HandleStatus( + state, state->ResumeDecodingStructureInPayload(&priority_fields_, db)); +} + +DecodeStatus PriorityPayloadDecoder::HandleStatus(FrameDecoderState* state, + DecodeStatus status) { + if (status == DecodeStatus::kDecodeDone) { + if (state->remaining_payload() == 0) { + state->listener()->OnPriorityFrame(state->frame_header(), + priority_fields_); + return DecodeStatus::kDecodeDone; + } + // Payload is too long. + return state->ReportFrameSizeError(); + } + // Not done decoding the structure. Either we've got more payload to decode, + // or we've run out because the payload is too short, in which case + // OnFrameSizeError will have already been called. + DCHECK( + (status == DecodeStatus::kDecodeInProgress && + state->remaining_payload() > 0) || + (status == DecodeStatus::kDecodeError && state->remaining_payload() == 0)) + << "\n status=" << status + << "; remaining_payload=" << state->remaining_payload(); + return status; +} + +} // namespace http2
diff --git a/http2/decoder/payload_decoders/priority_payload_decoder.h b/http2/decoder/payload_decoders/priority_payload_decoder.h new file mode 100644 index 0000000..921eefe --- /dev/null +++ b/http2/decoder/payload_decoders/priority_payload_decoder.h
@@ -0,0 +1,44 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PRIORITY_PAYLOAD_DECODER_H_ +#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PRIORITY_PAYLOAD_DECODER_H_ + +// Decodes the payload of a PRIORITY frame. + +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" + +namespace http2 { +namespace test { +class PriorityPayloadDecoderPeer; +} // namespace test + +class HTTP2_EXPORT_PRIVATE PriorityPayloadDecoder { + public: + // Starts the decoding of a PRIORITY frame's payload, and completes it if + // the entire payload is in the provided decode buffer. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a PRIORITY frame that has been split across decode + // buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); + + private: + friend class test::PriorityPayloadDecoderPeer; + + // Determines whether to report the PRIORITY to the listener, wait for more + // input, or to report a Frame Size Error. + DecodeStatus HandleStatus(FrameDecoderState* state, DecodeStatus status); + + Http2PriorityFields priority_fields_; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PRIORITY_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/priority_payload_decoder_test.cc b/http2/decoder/payload_decoders/priority_payload_decoder_test.cc new file mode 100644 index 0000000..4e44eba --- /dev/null +++ b/http2/decoder/payload_decoders/priority_payload_decoder_test.cc
@@ -0,0 +1,90 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures_test_util.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +namespace http2 { +namespace test { + +class PriorityPayloadDecoderPeer { + public: + static constexpr Http2FrameType FrameType() { + return Http2FrameType::PRIORITY; + } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; } +}; + +namespace { + +struct Listener : public FramePartsCollector { + void OnPriorityFrame(const Http2FrameHeader& header, + const Http2PriorityFields& priority_fields) override { + VLOG(1) << "OnPriority: " << header << "; " << priority_fields; + StartAndEndFrame(header)->OnPriorityFrame(header, priority_fields); + } + + void OnFrameSizeError(const Http2FrameHeader& header) override { + VLOG(1) << "OnFrameSizeError: " << header; + FrameError(header)->OnFrameSizeError(header); + } +}; + +class PriorityPayloadDecoderTest + : public AbstractPayloadDecoderTest<PriorityPayloadDecoder, + PriorityPayloadDecoderPeer, + Listener> { + protected: + Http2PriorityFields RandPriorityFields() { + Http2PriorityFields fields; + test::Randomize(&fields, RandomPtr()); + return fields; + } +}; + +// Confirm we get an error if the payload is not the correct size to hold +// exactly one Http2PriorityFields. +TEST_F(PriorityPayloadDecoderTest, WrongSize) { + auto approve_size = [](size_t size) { + return size != Http2PriorityFields::EncodedSize(); + }; + Http2FrameBuilder fb; + fb.Append(RandPriorityFields()); + fb.Append(RandPriorityFields()); + EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size)); +} + +TEST_F(PriorityPayloadDecoderTest, VariousPayloads) { + for (int n = 0; n < 100; ++n) { + Http2PriorityFields fields = RandPriorityFields(); + Http2FrameBuilder fb; + fb.Append(fields); + Http2FrameHeader header(fb.size(), Http2FrameType::PRIORITY, RandFlags(), + RandStreamId()); + set_frame_header(header); + FrameParts expected(header); + expected.SetOptPriority(fields); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected)); + } +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/decoder/payload_decoders/push_promise_payload_decoder.cc b/http2/decoder/payload_decoders/push_promise_payload_decoder.cc new file mode 100644 index 0000000..cec1c07 --- /dev/null +++ b/http2/decoder/payload_decoders/push_promise_payload_decoder.cc
@@ -0,0 +1,172 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h" + +namespace http2 { + +std::ostream& operator<<(std::ostream& out, + PushPromisePayloadDecoder::PayloadState v) { + switch (v) { + case PushPromisePayloadDecoder::PayloadState::kReadPadLength: + return out << "kReadPadLength"; + case PushPromisePayloadDecoder::PayloadState:: + kStartDecodingPushPromiseFields: + return out << "kStartDecodingPushPromiseFields"; + case PushPromisePayloadDecoder::PayloadState::kReadPayload: + return out << "kReadPayload"; + case PushPromisePayloadDecoder::PayloadState::kSkipPadding: + return out << "kSkipPadding"; + case PushPromisePayloadDecoder::PayloadState:: + kResumeDecodingPushPromiseFields: + return out << "kResumeDecodingPushPromiseFields"; + } + return out << static_cast<int>(v); +} + +DecodeStatus PushPromisePayloadDecoder::StartDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + const Http2FrameHeader& frame_header = state->frame_header(); + const uint32_t total_length = frame_header.payload_length; + + DVLOG(2) << "PushPromisePayloadDecoder::StartDecodingPayload: " + << frame_header; + + DCHECK_EQ(Http2FrameType::PUSH_PROMISE, frame_header.type); + DCHECK_LE(db->Remaining(), total_length); + DCHECK_EQ(0, frame_header.flags & + ~(Http2FrameFlag::END_HEADERS | Http2FrameFlag::PADDED)); + + if (!frame_header.IsPadded()) { + // If it turns out that PUSH_PROMISE frames without padding are sufficiently + // common, and that they are usually short enough that they fit entirely + // into one DecodeBuffer, we can detect that here and implement a special + // case, avoiding the state machine in ResumeDecodingPayload. + payload_state_ = PayloadState::kStartDecodingPushPromiseFields; + } else { + payload_state_ = PayloadState::kReadPadLength; + } + state->InitializeRemainders(); + return ResumeDecodingPayload(state, db); +} + +DecodeStatus PushPromisePayloadDecoder::ResumeDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "UnknownPayloadDecoder::ResumeDecodingPayload" + << " remaining_payload=" << state->remaining_payload() + << " db->Remaining=" << db->Remaining(); + + const Http2FrameHeader& frame_header = state->frame_header(); + DCHECK_EQ(Http2FrameType::PUSH_PROMISE, frame_header.type); + DCHECK_LE(state->remaining_payload(), frame_header.payload_length); + DCHECK_LE(db->Remaining(), frame_header.payload_length); + + DecodeStatus status; + while (true) { + DVLOG(2) + << "PushPromisePayloadDecoder::ResumeDecodingPayload payload_state_=" + << payload_state_; + switch (payload_state_) { + case PayloadState::kReadPadLength: + DCHECK_EQ(state->remaining_payload(), frame_header.payload_length); + // ReadPadLength handles the OnPadLength callback, and updating the + // remaining_payload and remaining_padding fields. If the amount of + // padding is too large to fit in the frame's payload, ReadPadLength + // instead calls OnPaddingTooLong and returns kDecodeError. + // Suppress the call to OnPadLength because we haven't yet called + // OnPushPromiseStart, which needs to wait until we've decoded the + // Promised Stream ID. + status = state->ReadPadLength(db, /*report_pad_length*/ false); + if (status != DecodeStatus::kDecodeDone) { + payload_state_ = PayloadState::kReadPadLength; + return status; + } + HTTP2_FALLTHROUGH; + + case PayloadState::kStartDecodingPushPromiseFields: + status = + state->StartDecodingStructureInPayload(&push_promise_fields_, db); + if (status != DecodeStatus::kDecodeDone) { + payload_state_ = PayloadState::kResumeDecodingPushPromiseFields; + return status; + } + // Finished decoding the Promised Stream ID. Can now tell the listener + // that we're starting to decode a PUSH_PROMISE frame. + ReportPushPromise(state); + HTTP2_FALLTHROUGH; + + case PayloadState::kReadPayload: + DCHECK_LT(state->remaining_payload(), frame_header.payload_length); + DCHECK_LE(state->remaining_payload(), + frame_header.payload_length - + Http2PushPromiseFields::EncodedSize()); + DCHECK_LE( + state->remaining_payload(), + frame_header.payload_length - + Http2PushPromiseFields::EncodedSize() - + (frame_header.IsPadded() ? (1 + state->remaining_padding()) + : 0)); + { + size_t avail = state->AvailablePayload(db); + state->listener()->OnHpackFragment(db->cursor(), avail); + db->AdvanceCursor(avail); + state->ConsumePayload(avail); + } + if (state->remaining_payload() > 0) { + payload_state_ = PayloadState::kReadPayload; + return DecodeStatus::kDecodeInProgress; + } + HTTP2_FALLTHROUGH; + + case PayloadState::kSkipPadding: + // SkipPadding handles the OnPadding callback. + if (state->SkipPadding(db)) { + state->listener()->OnPushPromiseEnd(); + return DecodeStatus::kDecodeDone; + } + payload_state_ = PayloadState::kSkipPadding; + return DecodeStatus::kDecodeInProgress; + + case PayloadState::kResumeDecodingPushPromiseFields: + status = + state->ResumeDecodingStructureInPayload(&push_promise_fields_, db); + if (status == DecodeStatus::kDecodeDone) { + // Finished decoding the Promised Stream ID. Can now tell the listener + // that we're starting to decode a PUSH_PROMISE frame. + ReportPushPromise(state); + payload_state_ = PayloadState::kReadPayload; + continue; + } + payload_state_ = PayloadState::kResumeDecodingPushPromiseFields; + return status; + } + HTTP2_BUG << "PayloadState: " << payload_state_; + } +} + +void PushPromisePayloadDecoder::ReportPushPromise(FrameDecoderState* state) { + const Http2FrameHeader& frame_header = state->frame_header(); + if (frame_header.IsPadded()) { + state->listener()->OnPushPromiseStart(frame_header, push_promise_fields_, + 1 + state->remaining_padding()); + } else { + state->listener()->OnPushPromiseStart(frame_header, push_promise_fields_, + 0); + } +} + +} // namespace http2
diff --git a/http2/decoder/payload_decoders/push_promise_payload_decoder.h b/http2/decoder/payload_decoders/push_promise_payload_decoder.h new file mode 100644 index 0000000..2db9cb3 --- /dev/null +++ b/http2/decoder/payload_decoders/push_promise_payload_decoder.h
@@ -0,0 +1,66 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PUSH_PROMISE_PAYLOAD_DECODER_H_ +#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PUSH_PROMISE_PAYLOAD_DECODER_H_ + +// Decodes the payload of a PUSH_PROMISE frame. + +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" + +namespace http2 { +namespace test { +class PushPromisePayloadDecoderPeer; +} // namespace test + +class HTTP2_EXPORT_PRIVATE PushPromisePayloadDecoder { + public: + // States during decoding of a PUSH_PROMISE frame. + enum class PayloadState { + // The frame is padded and we need to read the PAD_LENGTH field (1 byte). + kReadPadLength, + + // Ready to start decoding the fixed size fields of the PUSH_PROMISE + // frame into push_promise_fields_. + kStartDecodingPushPromiseFields, + + // The decoder has already called OnPushPromiseStart, and is now reporting + // the HPACK block fragment to the listener's OnHpackFragment method. + kReadPayload, + + // The decoder has finished with the HPACK block fragment, and is now + // ready to skip the trailing padding, if the frame has any. + kSkipPadding, + + // The fixed size fields weren't all available when the decoder first tried + // to decode them (state kStartDecodingPushPromiseFields); this state + // resumes the decoding when ResumeDecodingPayload is called later. + kResumeDecodingPushPromiseFields, + }; + + // Starts the decoding of a PUSH_PROMISE frame's payload, and completes it if + // the entire payload is in the provided decode buffer. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a PUSH_PROMISE frame's payload that has been split across + // decode buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); + + private: + friend class test::PushPromisePayloadDecoderPeer; + + void ReportPushPromise(FrameDecoderState* state); + + PayloadState payload_state_; + Http2PushPromiseFields push_promise_fields_; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PUSH_PROMISE_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/push_promise_payload_decoder_test.cc b/http2/decoder/payload_decoders/push_promise_payload_decoder_test.cc new file mode 100644 index 0000000..9d55a80 --- /dev/null +++ b/http2/decoder/payload_decoders/push_promise_payload_decoder_test.cc
@@ -0,0 +1,137 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures_test_util.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +namespace http2 { +namespace test { + +// Provides friend access to an instance of the payload decoder, and also +// provides info to aid in testing. +class PushPromisePayloadDecoderPeer { + public: + static constexpr Http2FrameType FrameType() { + return Http2FrameType::PUSH_PROMISE; + } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { + return Http2FrameFlag::PADDED; + } +}; + +namespace { + +// Listener listens for only those methods expected by the payload decoder +// under test, and forwards them onto the FrameParts instance for the current +// frame. +struct Listener : public FramePartsCollector { + void OnPushPromiseStart(const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) override { + VLOG(1) << "OnPushPromiseStart header: " << header + << " promise: " << promise + << " total_padding_length: " << total_padding_length; + EXPECT_EQ(Http2FrameType::PUSH_PROMISE, header.type); + StartFrame(header)->OnPushPromiseStart(header, promise, + total_padding_length); + } + + void OnHpackFragment(const char* data, size_t len) override { + VLOG(1) << "OnHpackFragment: len=" << len; + CurrentFrame()->OnHpackFragment(data, len); + } + + void OnPushPromiseEnd() override { + VLOG(1) << "OnPushPromiseEnd"; + EndFrame()->OnPushPromiseEnd(); + } + + void OnPadding(const char* padding, size_t skipped_length) override { + VLOG(1) << "OnPadding: " << skipped_length; + CurrentFrame()->OnPadding(padding, skipped_length); + } + + void OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) override { + VLOG(1) << "OnPaddingTooLong: " << header + << "; missing_length: " << missing_length; + FrameError(header)->OnPaddingTooLong(header, missing_length); + } + + void OnFrameSizeError(const Http2FrameHeader& header) override { + VLOG(1) << "OnFrameSizeError: " << header; + FrameError(header)->OnFrameSizeError(header); + } +}; + +class PushPromisePayloadDecoderTest + : public AbstractPaddablePayloadDecoderTest<PushPromisePayloadDecoder, + PushPromisePayloadDecoderPeer, + Listener> {}; + +INSTANTIATE_TEST_CASE_P(VariousPadLengths, + PushPromisePayloadDecoderTest, + ::testing::Values(0, 1, 2, 3, 4, 254, 255, 256)); + +// Payload contains the required Http2PushPromiseFields, followed by some +// (fake) HPACK payload. +TEST_P(PushPromisePayloadDecoderTest, VariousHpackPayloadSizes) { + for (size_t hpack_size : {0, 1, 2, 3, 255, 256, 1024}) { + LOG(INFO) << "########### hpack_size = " << hpack_size << " ###########"; + Reset(); + Http2String hpack_payload = Random().RandString(hpack_size); + Http2PushPromiseFields push_promise{RandStreamId()}; + frame_builder_.Append(push_promise); + frame_builder_.Append(hpack_payload); + MaybeAppendTrailingPadding(); + Http2FrameHeader frame_header(frame_builder_.size(), + Http2FrameType::PUSH_PROMISE, RandFlags(), + RandStreamId()); + set_frame_header(frame_header); + FrameParts expected(frame_header, hpack_payload, total_pad_length_); + expected.SetOptPushPromise(push_promise); + EXPECT_TRUE( + DecodePayloadAndValidateSeveralWays(frame_builder_.buffer(), expected)); + } +} + +// Confirm we get an error if the payload is not long enough for the required +// portion of the payload, regardless of the amount of (valid) padding. +TEST_P(PushPromisePayloadDecoderTest, Truncated) { + auto approve_size = [](size_t size) { + return size != Http2PushPromiseFields::EncodedSize(); + }; + Http2PushPromiseFields push_promise{RandStreamId()}; + Http2FrameBuilder fb; + fb.Append(push_promise); + EXPECT_TRUE(VerifyDetectsMultipleFrameSizeErrors(0, fb.buffer(), approve_size, + total_pad_length_)); +} + +// Confirm we get an error if the PADDED flag is set but the payload is not +// long enough to hold even the Pad Length amount of padding. +TEST_P(PushPromisePayloadDecoderTest, PaddingTooLong) { + EXPECT_TRUE(VerifyDetectsPaddingTooLong()); +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/decoder/payload_decoders/rst_stream_payload_decoder.cc b/http2/decoder/payload_decoders/rst_stream_payload_decoder.cc new file mode 100644 index 0000000..c39a16a --- /dev/null +++ b/http2/decoder/payload_decoders/rst_stream_payload_decoder.cc
@@ -0,0 +1,66 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" + +namespace http2 { + +DecodeStatus RstStreamPayloadDecoder::StartDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "RstStreamPayloadDecoder::StartDecodingPayload: " + << state->frame_header(); + DCHECK_EQ(Http2FrameType::RST_STREAM, state->frame_header().type); + DCHECK_LE(db->Remaining(), state->frame_header().payload_length); + // RST_STREAM has no flags. + DCHECK_EQ(0, state->frame_header().flags); + state->InitializeRemainders(); + return HandleStatus( + state, state->StartDecodingStructureInPayload(&rst_stream_fields_, db)); +} + +DecodeStatus RstStreamPayloadDecoder::ResumeDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "RstStreamPayloadDecoder::ResumeDecodingPayload" + << " remaining_payload=" << state->remaining_payload() + << " db->Remaining=" << db->Remaining(); + DCHECK_EQ(Http2FrameType::RST_STREAM, state->frame_header().type); + DCHECK_LE(db->Remaining(), state->frame_header().payload_length); + return HandleStatus( + state, state->ResumeDecodingStructureInPayload(&rst_stream_fields_, db)); +} + +DecodeStatus RstStreamPayloadDecoder::HandleStatus(FrameDecoderState* state, + DecodeStatus status) { + DVLOG(2) << "HandleStatus: status=" << status + << "; remaining_payload=" << state->remaining_payload(); + if (status == DecodeStatus::kDecodeDone) { + if (state->remaining_payload() == 0) { + state->listener()->OnRstStream(state->frame_header(), + rst_stream_fields_.error_code); + return DecodeStatus::kDecodeDone; + } + // Payload is too long. + return state->ReportFrameSizeError(); + } + // Not done decoding the structure. Either we've got more payload to decode, + // or we've run out because the payload is too short, in which case + // OnFrameSizeError will have already been called by the FrameDecoderState. + DCHECK( + (status == DecodeStatus::kDecodeInProgress && + state->remaining_payload() > 0) || + (status == DecodeStatus::kDecodeError && state->remaining_payload() == 0)) + << "\n status=" << status + << "; remaining_payload=" << state->remaining_payload(); + return status; +} + +} // namespace http2
diff --git a/http2/decoder/payload_decoders/rst_stream_payload_decoder.h b/http2/decoder/payload_decoders/rst_stream_payload_decoder.h new file mode 100644 index 0000000..947fc06 --- /dev/null +++ b/http2/decoder/payload_decoders/rst_stream_payload_decoder.h
@@ -0,0 +1,42 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_RST_STREAM_PAYLOAD_DECODER_H_ +#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_RST_STREAM_PAYLOAD_DECODER_H_ + +// Decodes the payload of a RST_STREAM frame. + +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" + +namespace http2 { +namespace test { +class RstStreamPayloadDecoderPeer; +} // namespace test + +class HTTP2_EXPORT_PRIVATE RstStreamPayloadDecoder { + public: + // Starts the decoding of a RST_STREAM frame's payload, and completes it if + // the entire payload is in the provided decode buffer. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a RST_STREAM frame's payload that has been split across + // decode buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); + + private: + friend class test::RstStreamPayloadDecoderPeer; + + DecodeStatus HandleStatus(FrameDecoderState* state, DecodeStatus status); + + Http2RstStreamFields rst_stream_fields_; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_RST_STREAM_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/rst_stream_payload_decoder_test.cc b/http2/decoder/payload_decoders/rst_stream_payload_decoder_test.cc new file mode 100644 index 0000000..08f6621 --- /dev/null +++ b/http2/decoder/payload_decoders/rst_stream_payload_decoder_test.cc
@@ -0,0 +1,92 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_constants_test_util.h" +#include "net/third_party/quiche/src/http2/http2_structures_test_util.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +namespace http2 { +namespace test { + +class RstStreamPayloadDecoderPeer { + public: + static constexpr Http2FrameType FrameType() { + return Http2FrameType::RST_STREAM; + } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; } +}; + +namespace { + +struct Listener : public FramePartsCollector { + void OnRstStream(const Http2FrameHeader& header, + Http2ErrorCode error_code) override { + VLOG(1) << "OnRstStream: " << header << "; error_code=" << error_code; + StartAndEndFrame(header)->OnRstStream(header, error_code); + } + + void OnFrameSizeError(const Http2FrameHeader& header) override { + VLOG(1) << "OnFrameSizeError: " << header; + FrameError(header)->OnFrameSizeError(header); + } +}; + +class RstStreamPayloadDecoderTest + : public AbstractPayloadDecoderTest<RstStreamPayloadDecoder, + RstStreamPayloadDecoderPeer, + Listener> { + protected: + Http2RstStreamFields RandRstStreamFields() { + Http2RstStreamFields fields; + test::Randomize(&fields, RandomPtr()); + return fields; + } +}; + +// Confirm we get an error if the payload is not the correct size to hold +// exactly one Http2RstStreamFields. +TEST_F(RstStreamPayloadDecoderTest, WrongSize) { + auto approve_size = [](size_t size) { + return size != Http2RstStreamFields::EncodedSize(); + }; + Http2FrameBuilder fb; + fb.Append(RandRstStreamFields()); + fb.Append(RandRstStreamFields()); + fb.Append(RandRstStreamFields()); + EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size)); +} + +TEST_F(RstStreamPayloadDecoderTest, AllErrors) { + for (auto error_code : AllHttp2ErrorCodes()) { + Http2RstStreamFields fields{error_code}; + Http2FrameBuilder fb; + fb.Append(fields); + Http2FrameHeader header(fb.size(), Http2FrameType::RST_STREAM, RandFlags(), + RandStreamId()); + set_frame_header(header); + FrameParts expected(header); + expected.SetOptRstStreamErrorCode(error_code); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected)); + } +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/decoder/payload_decoders/settings_payload_decoder.cc b/http2/decoder/payload_decoders/settings_payload_decoder.cc new file mode 100644 index 0000000..bf29c4d --- /dev/null +++ b/http2/decoder/payload_decoders/settings_payload_decoder.cc
@@ -0,0 +1,97 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" + +namespace http2 { + +DecodeStatus SettingsPayloadDecoder::StartDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + const Http2FrameHeader& frame_header = state->frame_header(); + const uint32_t total_length = frame_header.payload_length; + + DVLOG(2) << "SettingsPayloadDecoder::StartDecodingPayload: " << frame_header; + DCHECK_EQ(Http2FrameType::SETTINGS, frame_header.type); + DCHECK_LE(db->Remaining(), total_length); + DCHECK_EQ(0, frame_header.flags & ~(Http2FrameFlag::ACK)); + + if (frame_header.IsAck()) { + if (total_length == 0) { + state->listener()->OnSettingsAck(frame_header); + return DecodeStatus::kDecodeDone; + } else { + state->InitializeRemainders(); + return state->ReportFrameSizeError(); + } + } else { + state->InitializeRemainders(); + state->listener()->OnSettingsStart(frame_header); + return StartDecodingSettings(state, db); + } +} + +DecodeStatus SettingsPayloadDecoder::ResumeDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "SettingsPayloadDecoder::ResumeDecodingPayload" + << " remaining_payload=" << state->remaining_payload() + << " db->Remaining=" << db->Remaining(); + DCHECK_EQ(Http2FrameType::SETTINGS, state->frame_header().type); + DCHECK_LE(db->Remaining(), state->frame_header().payload_length); + + DecodeStatus status = + state->ResumeDecodingStructureInPayload(&setting_fields_, db); + if (status == DecodeStatus::kDecodeDone) { + state->listener()->OnSetting(setting_fields_); + return StartDecodingSettings(state, db); + } + return HandleNotDone(state, db, status); +} + +DecodeStatus SettingsPayloadDecoder::StartDecodingSettings( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "SettingsPayloadDecoder::StartDecodingSettings" + << " remaining_payload=" << state->remaining_payload() + << " db->Remaining=" << db->Remaining(); + while (state->remaining_payload() > 0) { + DecodeStatus status = + state->StartDecodingStructureInPayload(&setting_fields_, db); + if (status == DecodeStatus::kDecodeDone) { + state->listener()->OnSetting(setting_fields_); + continue; + } + return HandleNotDone(state, db, status); + } + DVLOG(2) << "LEAVING SettingsPayloadDecoder::StartDecodingSettings" + << "\n\tdb->Remaining=" << db->Remaining() + << "\n\t remaining_payload=" << state->remaining_payload(); + state->listener()->OnSettingsEnd(); + return DecodeStatus::kDecodeDone; +} + +DecodeStatus SettingsPayloadDecoder::HandleNotDone(FrameDecoderState* state, + DecodeBuffer* db, + DecodeStatus status) { + // Not done decoding the structure. Either we've got more payload to decode, + // or we've run out because the payload is too short, in which case + // OnFrameSizeError will have already been called. + DCHECK( + (status == DecodeStatus::kDecodeInProgress && + state->remaining_payload() > 0) || + (status == DecodeStatus::kDecodeError && state->remaining_payload() == 0)) + << "\n status=" << status + << "; remaining_payload=" << state->remaining_payload() + << "; db->Remaining=" << db->Remaining(); + return status; +} + +} // namespace http2
diff --git a/http2/decoder/payload_decoders/settings_payload_decoder.h b/http2/decoder/payload_decoders/settings_payload_decoder.h new file mode 100644 index 0000000..7e3c313 --- /dev/null +++ b/http2/decoder/payload_decoders/settings_payload_decoder.h
@@ -0,0 +1,54 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_SETTINGS_PAYLOAD_DECODER_H_ +#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_SETTINGS_PAYLOAD_DECODER_H_ + +// Decodes the payload of a SETTINGS frame; for the RFC, see: +// http://httpwg.org/specs/rfc7540.html#SETTINGS + +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" + +namespace http2 { +namespace test { +class SettingsPayloadDecoderPeer; +} // namespace test + +class HTTP2_EXPORT_PRIVATE SettingsPayloadDecoder { + public: + // Starts the decoding of a SETTINGS frame's payload, and completes it if + // the entire payload is in the provided decode buffer. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a SETTINGS frame that has been split across decode + // buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); + + private: + friend class test::SettingsPayloadDecoderPeer; + + // Decodes as many settings as are available in the decode buffer, starting at + // the first byte of one setting; if a single setting is split across buffers, + // ResumeDecodingPayload will handle starting from where the previous call + // left off, and then will call StartDecodingSettings. + DecodeStatus StartDecodingSettings(FrameDecoderState* state, + DecodeBuffer* db); + + // Decoding a single SETTING returned a status other than kDecodeDone; this + // method just brings together the DCHECKs to reduce duplication. + DecodeStatus HandleNotDone(FrameDecoderState* state, + DecodeBuffer* db, + DecodeStatus status); + + Http2SettingFields setting_fields_; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_SETTINGS_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/settings_payload_decoder_test.cc b/http2/decoder/payload_decoders/settings_payload_decoder_test.cc new file mode 100644 index 0000000..3429779 --- /dev/null +++ b/http2/decoder/payload_decoders/settings_payload_decoder_test.cc
@@ -0,0 +1,160 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder.h" + +#include <stddef.h> + +#include <vector> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_constants_test_util.h" +#include "net/third_party/quiche/src/http2/http2_structures_test_util.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +namespace http2 { +namespace test { + +class SettingsPayloadDecoderPeer { + public: + static constexpr Http2FrameType FrameType() { + return Http2FrameType::SETTINGS; + } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { + return Http2FrameFlag::ACK; + } +}; + +namespace { + +struct Listener : public FramePartsCollector { + void OnSettingsStart(const Http2FrameHeader& header) override { + VLOG(1) << "OnSettingsStart: " << header; + EXPECT_EQ(Http2FrameType::SETTINGS, header.type) << header; + EXPECT_EQ(Http2FrameFlag(), header.flags) << header; + StartFrame(header)->OnSettingsStart(header); + } + + void OnSetting(const Http2SettingFields& setting_fields) override { + VLOG(1) << "Http2SettingFields: setting_fields=" << setting_fields; + CurrentFrame()->OnSetting(setting_fields); + } + + void OnSettingsEnd() override { + VLOG(1) << "OnSettingsEnd"; + EndFrame()->OnSettingsEnd(); + } + + void OnSettingsAck(const Http2FrameHeader& header) override { + VLOG(1) << "OnSettingsAck: " << header; + StartAndEndFrame(header)->OnSettingsAck(header); + } + + void OnFrameSizeError(const Http2FrameHeader& header) override { + VLOG(1) << "OnFrameSizeError: " << header; + FrameError(header)->OnFrameSizeError(header); + } +}; + +class SettingsPayloadDecoderTest + : public AbstractPayloadDecoderTest<SettingsPayloadDecoder, + SettingsPayloadDecoderPeer, + Listener> { + protected: + Http2SettingFields RandSettingsFields() { + Http2SettingFields fields; + test::Randomize(&fields, RandomPtr()); + return fields; + } +}; + +// Confirm we get an error if the SETTINGS payload is not the correct size +// to hold exactly zero or more whole Http2SettingFields. +TEST_F(SettingsPayloadDecoderTest, SettingsWrongSize) { + auto approve_size = [](size_t size) { + // Should get an error if size is not an integral multiple of the size + // of one setting. + return 0 != (size % Http2SettingFields::EncodedSize()); + }; + Http2FrameBuilder fb; + fb.Append(RandSettingsFields()); + fb.Append(RandSettingsFields()); + fb.Append(RandSettingsFields()); + EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size)); +} + +// Confirm we get an error if the SETTINGS ACK payload is not empty. +TEST_F(SettingsPayloadDecoderTest, SettingsAkcWrongSize) { + auto approve_size = [](size_t size) { return size != 0; }; + Http2FrameBuilder fb; + fb.Append(RandSettingsFields()); + fb.Append(RandSettingsFields()); + fb.Append(RandSettingsFields()); + EXPECT_TRUE(VerifyDetectsFrameSizeError(Http2FrameFlag::ACK, fb.buffer(), + approve_size)); +} + +// SETTINGS must have stream_id==0, but the payload decoder doesn't check that. +TEST_F(SettingsPayloadDecoderTest, SettingsAck) { + for (int stream_id = 0; stream_id < 3; ++stream_id) { + Http2FrameHeader header(0, Http2FrameType::SETTINGS, + RandFlags() | Http2FrameFlag::ACK, stream_id); + set_frame_header(header); + FrameParts expected(header); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays("", expected)); + } +} + +// Try several values of each known SETTINGS parameter. +TEST_F(SettingsPayloadDecoderTest, OneRealSetting) { + std::vector<uint32_t> values = {0, 1, 0xffffffff, Random().Rand32()}; + for (auto param : AllHttp2SettingsParameters()) { + for (uint32_t value : values) { + Http2SettingFields fields(param, value); + Http2FrameBuilder fb; + fb.Append(fields); + Http2FrameHeader header(fb.size(), Http2FrameType::SETTINGS, RandFlags(), + RandStreamId()); + set_frame_header(header); + FrameParts expected(header); + expected.AppendSetting(fields); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected)); + } + } +} + +// Decode a SETTINGS frame with lots of fields. +TEST_F(SettingsPayloadDecoderTest, ManySettings) { + const size_t num_settings = 100; + const size_t size = Http2SettingFields::EncodedSize() * num_settings; + Http2FrameHeader header(size, Http2FrameType::SETTINGS, + RandFlags(), // & ~Http2FrameFlag::ACK, + RandStreamId()); + set_frame_header(header); + FrameParts expected(header); + Http2FrameBuilder fb; + for (size_t n = 0; n < num_settings; ++n) { + Http2SettingFields fields(static_cast<Http2SettingsParameter>(n), + Random().Rand32()); + fb.Append(fields); + expected.AppendSetting(fields); + } + ASSERT_EQ(size, fb.size()); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected)); +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/decoder/payload_decoders/unknown_payload_decoder.cc b/http2/decoder/payload_decoders/unknown_payload_decoder.cc new file mode 100644 index 0000000..10eaf24 --- /dev/null +++ b/http2/decoder/payload_decoders/unknown_payload_decoder.cc
@@ -0,0 +1,55 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" + +namespace http2 { + +DecodeStatus UnknownPayloadDecoder::StartDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + const Http2FrameHeader& frame_header = state->frame_header(); + + DVLOG(2) << "UnknownPayloadDecoder::StartDecodingPayload: " << frame_header; + DCHECK(!IsSupportedHttp2FrameType(frame_header.type)) << frame_header; + DCHECK_LE(db->Remaining(), frame_header.payload_length); + + state->InitializeRemainders(); + state->listener()->OnUnknownStart(frame_header); + return ResumeDecodingPayload(state, db); +} + +DecodeStatus UnknownPayloadDecoder::ResumeDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "UnknownPayloadDecoder::ResumeDecodingPayload " + << "remaining_payload=" << state->remaining_payload() + << "; db->Remaining=" << db->Remaining(); + DCHECK(!IsSupportedHttp2FrameType(state->frame_header().type)) + << state->frame_header(); + DCHECK_LE(state->remaining_payload(), state->frame_header().payload_length); + DCHECK_LE(db->Remaining(), state->remaining_payload()); + + size_t avail = db->Remaining(); + if (avail > 0) { + state->listener()->OnUnknownPayload(db->cursor(), avail); + db->AdvanceCursor(avail); + state->ConsumePayload(avail); + } + if (state->remaining_payload() == 0) { + state->listener()->OnUnknownEnd(); + return DecodeStatus::kDecodeDone; + } + return DecodeStatus::kDecodeInProgress; +} + +} // namespace http2
diff --git a/http2/decoder/payload_decoders/unknown_payload_decoder.h b/http2/decoder/payload_decoders/unknown_payload_decoder.h new file mode 100644 index 0000000..7bf4103 --- /dev/null +++ b/http2/decoder/payload_decoders/unknown_payload_decoder.h
@@ -0,0 +1,33 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_UNKNOWN_PAYLOAD_DECODER_H_ +#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_UNKNOWN_PAYLOAD_DECODER_H_ + +// Decodes the payload of a frame whose type unknown. According to the HTTP/2 +// specification (http://httpwg.org/specs/rfc7540.html#FrameHeader): +// Implementations MUST ignore and discard any frame that has +// a type that is unknown. + +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" + +namespace http2 { + +class HTTP2_EXPORT_PRIVATE UnknownPayloadDecoder { + public: + // Starts decoding a payload of unknown type; just passes it to the listener. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a payload of unknown type that has been split across + // decode buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_UNKNOWN_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/unknown_payload_decoder_test.cc b/http2/decoder/payload_decoders/unknown_payload_decoder_test.cc new file mode 100644 index 0000000..7ba95f7 --- /dev/null +++ b/http2/decoder/payload_decoders/unknown_payload_decoder_test.cc
@@ -0,0 +1,100 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder.h" + +#include <stddef.h> + +#include <type_traits> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +namespace http2 { +namespace test { +namespace { +Http2FrameType g_unknown_frame_type; +} // namespace + +// Provides friend access to an instance of the payload decoder, and also +// provides info to aid in testing. +class UnknownPayloadDecoderPeer { + public: + static Http2FrameType FrameType() { return g_unknown_frame_type; } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; } +}; + +namespace { + +struct Listener : public FramePartsCollector { + void OnUnknownStart(const Http2FrameHeader& header) override { + VLOG(1) << "OnUnknownStart: " << header; + StartFrame(header)->OnUnknownStart(header); + } + + void OnUnknownPayload(const char* data, size_t len) override { + VLOG(1) << "OnUnknownPayload: len=" << len; + CurrentFrame()->OnUnknownPayload(data, len); + } + + void OnUnknownEnd() override { + VLOG(1) << "OnUnknownEnd"; + EndFrame()->OnUnknownEnd(); + } +}; + +constexpr bool SupportedFrameType = false; + +class UnknownPayloadDecoderTest + : public AbstractPayloadDecoderTest<UnknownPayloadDecoder, + UnknownPayloadDecoderPeer, + Listener, + SupportedFrameType>, + public ::testing::WithParamInterface<uint32_t> { + protected: + UnknownPayloadDecoderTest() : length_(GetParam()) { + VLOG(1) << "################ length_=" << length_ << " ################"; + + // Each test case will choose a random frame type that isn't supported. + do { + g_unknown_frame_type = static_cast<Http2FrameType>(Random().Rand8()); + } while (IsSupportedHttp2FrameType(g_unknown_frame_type)); + } + + const uint32_t length_; +}; + +INSTANTIATE_TEST_CASE_P(VariousLengths, + UnknownPayloadDecoderTest, + ::testing::Values(0, 1, 2, 3, 255, 256)); + +TEST_P(UnknownPayloadDecoderTest, ValidLength) { + Http2String unknown_payload = Random().RandString(length_); + Http2FrameHeader frame_header(length_, g_unknown_frame_type, Random().Rand8(), + RandStreamId()); + set_frame_header(frame_header); + FrameParts expected(frame_header, unknown_payload); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(unknown_payload, expected)); + // TODO(jamessynge): Check here (and in other such tests) that the fast + // and slow decode counts are both non-zero. Perhaps also add some kind of + // test for the listener having been called. That could simply be a test + // that there is a single collected FrameParts instance, and that it matches + // expected. +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/decoder/payload_decoders/window_update_payload_decoder.cc b/http2/decoder/payload_decoders/window_update_payload_decoder.cc new file mode 100644 index 0000000..c0bb028 --- /dev/null +++ b/http2/decoder/payload_decoders/window_update_payload_decoder.cc
@@ -0,0 +1,82 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_http2_structures.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" + +namespace http2 { + +DecodeStatus WindowUpdatePayloadDecoder::StartDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + const Http2FrameHeader& frame_header = state->frame_header(); + const uint32_t total_length = frame_header.payload_length; + + DVLOG(2) << "WindowUpdatePayloadDecoder::StartDecodingPayload: " + << frame_header; + + DCHECK_EQ(Http2FrameType::WINDOW_UPDATE, frame_header.type); + DCHECK_LE(db->Remaining(), total_length); + + // WINDOW_UPDATE frames have no flags. + DCHECK_EQ(0, frame_header.flags); + + // Special case for when the payload is the correct size and entirely in + // the buffer. + if (db->Remaining() == Http2WindowUpdateFields::EncodedSize() && + total_length == Http2WindowUpdateFields::EncodedSize()) { + DoDecode(&window_update_fields_, db); + state->listener()->OnWindowUpdate( + frame_header, window_update_fields_.window_size_increment); + return DecodeStatus::kDecodeDone; + } + state->InitializeRemainders(); + return HandleStatus(state, state->StartDecodingStructureInPayload( + &window_update_fields_, db)); +} + +DecodeStatus WindowUpdatePayloadDecoder::ResumeDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "ResumeDecodingPayload: remaining_payload=" + << state->remaining_payload() + << "; db->Remaining=" << db->Remaining(); + DCHECK_EQ(Http2FrameType::WINDOW_UPDATE, state->frame_header().type); + DCHECK_LE(db->Remaining(), state->frame_header().payload_length); + return HandleStatus(state, state->ResumeDecodingStructureInPayload( + &window_update_fields_, db)); +} + +DecodeStatus WindowUpdatePayloadDecoder::HandleStatus(FrameDecoderState* state, + DecodeStatus status) { + DVLOG(2) << "HandleStatus: status=" << status + << "; remaining_payload=" << state->remaining_payload(); + if (status == DecodeStatus::kDecodeDone) { + if (state->remaining_payload() == 0) { + state->listener()->OnWindowUpdate( + state->frame_header(), window_update_fields_.window_size_increment); + return DecodeStatus::kDecodeDone; + } + // Payload is too long. + return state->ReportFrameSizeError(); + } + // Not done decoding the structure. Either we've got more payload to decode, + // or we've run out because the payload is too short, in which case + // OnFrameSizeError will have already been called. + DCHECK( + (status == DecodeStatus::kDecodeInProgress && + state->remaining_payload() > 0) || + (status == DecodeStatus::kDecodeError && state->remaining_payload() == 0)) + << "\n status=" << status + << "; remaining_payload=" << state->remaining_payload(); + return status; +} + +} // namespace http2
diff --git a/http2/decoder/payload_decoders/window_update_payload_decoder.h b/http2/decoder/payload_decoders/window_update_payload_decoder.h new file mode 100644 index 0000000..158c165 --- /dev/null +++ b/http2/decoder/payload_decoders/window_update_payload_decoder.h
@@ -0,0 +1,42 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_WINDOW_UPDATE_PAYLOAD_DECODER_H_ +#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_WINDOW_UPDATE_PAYLOAD_DECODER_H_ + +// Decodes the payload of a WINDOW_UPDATE frame. + +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" + +namespace http2 { +namespace test { +class WindowUpdatePayloadDecoderPeer; +} // namespace test + +class HTTP2_EXPORT_PRIVATE WindowUpdatePayloadDecoder { + public: + // Starts decoding a WINDOW_UPDATE frame's payload, and completes it if + // the entire payload is in the provided decode buffer. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a WINDOW_UPDATE frame's payload that has been split across + // decode buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); + + private: + friend class test::WindowUpdatePayloadDecoderPeer; + + DecodeStatus HandleStatus(FrameDecoderState* state, DecodeStatus status); + + Http2WindowUpdateFields window_update_fields_; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_WINDOW_UPDATE_PAYLOAD_DECODER_H_
diff --git a/http2/decoder/payload_decoders/window_update_payload_decoder_test.cc b/http2/decoder/payload_decoders/window_update_payload_decoder_test.cc new file mode 100644 index 0000000..77a2b90 --- /dev/null +++ b/http2/decoder/payload_decoders/window_update_payload_decoder_test.cc
@@ -0,0 +1,95 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures_test_util.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +namespace http2 { +namespace test { + +class WindowUpdatePayloadDecoderPeer { + public: + static constexpr Http2FrameType FrameType() { + return Http2FrameType::WINDOW_UPDATE; + } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; } +}; + +namespace { + +struct Listener : public FramePartsCollector { + void OnWindowUpdate(const Http2FrameHeader& header, + uint32_t window_size_increment) override { + VLOG(1) << "OnWindowUpdate: " << header + << "; window_size_increment=" << window_size_increment; + EXPECT_EQ(Http2FrameType::WINDOW_UPDATE, header.type); + StartAndEndFrame(header)->OnWindowUpdate(header, window_size_increment); + } + + void OnFrameSizeError(const Http2FrameHeader& header) override { + VLOG(1) << "OnFrameSizeError: " << header; + FrameError(header)->OnFrameSizeError(header); + } +}; + +class WindowUpdatePayloadDecoderTest + : public AbstractPayloadDecoderTest<WindowUpdatePayloadDecoder, + WindowUpdatePayloadDecoderPeer, + Listener> { + protected: + Http2WindowUpdateFields RandWindowUpdateFields() { + Http2WindowUpdateFields fields; + test::Randomize(&fields, RandomPtr()); + VLOG(3) << "RandWindowUpdateFields: " << fields; + return fields; + } +}; + +// Confirm we get an error if the payload is not the correct size to hold +// exactly one Http2WindowUpdateFields. +TEST_F(WindowUpdatePayloadDecoderTest, WrongSize) { + auto approve_size = [](size_t size) { + return size != Http2WindowUpdateFields::EncodedSize(); + }; + Http2FrameBuilder fb; + fb.Append(RandWindowUpdateFields()); + fb.Append(RandWindowUpdateFields()); + fb.Append(RandWindowUpdateFields()); + EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size)); +} + +TEST_F(WindowUpdatePayloadDecoderTest, VariousPayloads) { + for (int n = 0; n < 100; ++n) { + uint32_t stream_id = n == 0 ? 0 : RandStreamId(); + Http2WindowUpdateFields fields = RandWindowUpdateFields(); + Http2FrameBuilder fb; + fb.Append(fields); + Http2FrameHeader header(fb.size(), Http2FrameType::WINDOW_UPDATE, + RandFlags(), stream_id); + set_frame_header(header); + FrameParts expected(header); + expected.SetOptWindowUpdateIncrement(fields.window_size_increment); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected)); + } +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/hpack/decoder/hpack_block_collector.cc b/http2/hpack/decoder/hpack_block_collector.cc new file mode 100644 index 0000000..5b2c6b7 --- /dev/null +++ b/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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.h" + +#include <algorithm> +#include <memory> + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_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()); + 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 Http2String& value) { + entries_.push_back(HpackEntryCollector(type, index, value_huffman, value)); +} +void HpackBlockCollector::ExpectLiteralNameAndValue(HpackEntryType type, + bool name_huffman, + const Http2String& name, + bool value_huffman, + const Http2String& 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 { + 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, + Http2StringPiece 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, + Http2StringPiece expected_name, + bool expected_value_huffman, + Http2StringPiece 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/http2/hpack/decoder/hpack_block_collector.h b/http2/hpack/decoder/hpack_block_collector.h new file mode 100644 index 0000000..6ad1405 --- /dev/null +++ b/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 <vector> + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" + +namespace http2 { +namespace test { + +class 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 Http2String& value); + + // Add an HPACK entry for a header entry with a literal name and value. + void ExpectLiteralNameAndValue(HpackEntryType type, + bool name_huffman, + const Http2String& name, + bool value_huffman, + const Http2String& 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, + Http2StringPiece 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, + Http2StringPiece expected_name, + bool expected_value_huffman, + Http2StringPiece 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/http2/hpack/decoder/hpack_block_decoder.cc b/http2/hpack/decoder/hpack_block_decoder.cc new file mode 100644 index 0000000..c6882d2 --- /dev/null +++ b/http2/hpack/decoder/hpack_block_decoder.cc
@@ -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. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.h" + +#include <cstdint> + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { + +DecodeStatus HpackBlockDecoder::Decode(DecodeBuffer* db) { + if (!before_entry_) { + 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: + DCHECK_EQ(0u, db->Remaining()); + return DecodeStatus::kDecodeInProgress; + case DecodeStatus::kDecodeError: + return DecodeStatus::kDecodeError; + } + } + DCHECK(before_entry_); + while (db->HasData()) { + 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: + DCHECK_EQ(0u, db->Remaining()); + before_entry_ = false; + return DecodeStatus::kDecodeInProgress; + case DecodeStatus::kDecodeError: + return DecodeStatus::kDecodeError; + } + DCHECK(false); + } + DCHECK(before_entry_); + return DecodeStatus::kDecodeDone; +} + +Http2String HpackBlockDecoder::DebugString() const { + return Http2StrCat("HpackBlockDecoder(", entry_decoder_.DebugString(), + ", listener@", + Http2Hex(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/http2/hpack/decoder/hpack_block_decoder.h b/http2/hpack/decoder/hpack_block_decoder.h new file mode 100644 index 0000000..e8b23e1 --- /dev/null +++ b/http2/hpack/decoder/hpack_block_decoder.h
@@ -0,0 +1,64 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_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 "base/logging.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" + +namespace http2 { + +class HTTP2_EXPORT_PRIVATE HpackBlockDecoder { + public: + explicit HpackBlockDecoder(HpackEntryDecoderListener* listener) + : listener_(listener) { + 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() { + 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_; } + + Http2String DebugString() const; + + private: + HpackEntryDecoder entry_decoder_; + HpackEntryDecoderListener* const listener_; + bool before_entry_ = true; +}; + +HTTP2_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/http2/hpack/decoder/hpack_block_decoder_test.cc b/http2/hpack/decoder/hpack_block_decoder_test.cc new file mode 100644 index 0000000..fce45b2 --- /dev/null +++ b/http2/hpack/decoder/hpack_block_decoder_test.cc
@@ -0,0 +1,294 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.h" + +// Tests of HpackBlockDecoder. + +#include <cstdint> + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h" +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_example.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +using ::testing::AssertionSuccess; + +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( + Http2StringPiece hpack_example, + Validator validator) { + Http2String input = HpackExampleToStringOrDie(hpack_example); + DecodeBuffer db(input); + return DecodeAndValidateSeveralWays(&db, validator); + } + + uint8_t Rand8() { return Random().Rand8(); } + + Http2String 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]() { + VERIFY_AND_RETURN_SUCCESS(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]() { + VERIFY_AND_RETURN_SUCCESS(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]() { + VERIFY_AND_RETURN_SUCCESS(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]() { + VERIFY_AND_RETURN_SUCCESS(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) { + Http2String 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]() { + VERIFY_AND_RETURN_SUCCESS(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) { + Http2String 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]() { + VERIFY_AND_RETURN_SUCCESS(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]() { + VERIFY_AND_RETURN_SUCCESS(collector_.VerifyEq(expected)); + }; + EXPECT_TRUE( + DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder.cc b/http2/hpack/decoder/hpack_decoder.cc new file mode 100644 index 0000000..d6897cb --- /dev/null +++ b/http2/hpack/decoder/hpack_decoder.cc
@@ -0,0 +1,122 @@ +// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.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_detected_(false) {} + +HpackDecoder::~HpackDecoder() = default; + +void HpackDecoder::set_tables_debug_listener( + HpackDecoderTablesDebugListener* debug_listener) { + decoder_state_.set_tables_debug_listener(debug_listener); +} + +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() { + DVLOG(3) << "HpackDecoder::StartDecodingBlock, error_detected=" + << (error_detected() ? "true" : "false"); + if (error_detected()) { + 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) { + DVLOG(3) << "HpackDecoder::DecodeFragment, error_detected=" + << (error_detected() ? "true" : "false") + << ", size=" << db->Remaining(); + if (error_detected()) { + 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) { + // This has probably already been reported, but just in case... + ReportError("HPACK block malformed."); + return false; + } else if (error_detected()) { + return false; + } + // Should be positioned between entries iff decoding is complete. + DCHECK_EQ(block_decoder_.before_entry(), status == DecodeStatus::kDecodeDone) + << status; + if (!block_decoder_.before_entry()) { + entry_buffer_.BufferStringsIfUnbuffered(); + } + return true; +} + +bool HpackDecoder::EndDecodingBlock() { + DVLOG(3) << "HpackDecoder::EndDecodingBlock, error_detected=" + << (error_detected() ? "true" : "false"); + if (error_detected()) { + return false; + } + if (!block_decoder_.before_entry()) { + // The HPACK block ended in the middle of an entry. + ReportError("HPACK block truncated."); + return false; + } + decoder_state_.OnHeaderBlockEnd(); + if (error_detected()) { + // HpackDecoderState will have reported the error. + return false; + } + return true; +} + +bool HpackDecoder::error_detected() { + if (!error_detected_) { + if (entry_buffer_.error_detected()) { + DVLOG(2) << "HpackDecoder::error_detected in entry_buffer_"; + error_detected_ = true; + } else if (decoder_state_.error_detected()) { + DVLOG(2) << "HpackDecoder::error_detected in decoder_state_"; + error_detected_ = true; + } + } + return error_detected_; +} + +size_t HpackDecoder::EstimateMemoryUsage() const { + return Http2EstimateMemoryUsage(entry_buffer_); +} + +void HpackDecoder::ReportError(Http2StringPiece error_message) { + DVLOG(3) << "HpackDecoder::ReportError is new=" + << (!error_detected_ ? "true" : "false") + << ", error_message: " << error_message; + if (!error_detected_) { + error_detected_ = true; + decoder_state_.listener()->OnHeaderErrorDetected(error_message); + } +} + +} // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder.h b/http2/hpack/decoder/hpack_decoder.h new file mode 100644 index 0000000..e173bc6 --- /dev/null +++ b/http2/hpack/decoder/hpack_decoder.h
@@ -0,0 +1,123 @@ +// 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 "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { +namespace test { +class HpackDecoderPeer; +} // namespace test + +class HTTP2_EXPORT_PRIVATE HpackDecoder { + public: + HpackDecoder(HpackDecoderListener* listener, size_t max_string_size); + virtual ~HpackDecoder(); + + HpackDecoder(const HpackDecoder&) = delete; + HpackDecoder& operator=(const HpackDecoder&) = delete; + + // Set listener to be notified of insertions into the HPACK dynamic table, + // and uses of those entries. + void set_tables_debug_listener( + HpackDecoderTablesDebugListener* debug_listener); + + // 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); + + // 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(); + + // Was an error detected? + bool error_detected(); + + // Returns the estimate of dynamically allocated memory in bytes. + size_t EstimateMemoryUsage() const; + + private: + friend class test::HpackDecoderPeer; + + // Reports an error to the listener IF this is the first error detected. + void ReportError(Http2StringPiece error_message); + + // 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_; + + // Has an error been detected? + bool error_detected_; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_H_
diff --git a/http2/hpack/decoder/hpack_decoder_listener.cc b/http2/hpack/decoder/hpack_decoder_listener.cc new file mode 100644 index 0000000..8afa8aa --- /dev/null +++ b/http2/hpack/decoder/hpack_decoder_listener.cc
@@ -0,0 +1,30 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/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(HpackEntryType entry_type, + const HpackString& name, + const HpackString& value) {} +void HpackDecoderNoOpListener::OnHeaderListEnd() {} +void HpackDecoderNoOpListener::OnHeaderErrorDetected( + Http2StringPiece error_message) {} + +// static +HpackDecoderNoOpListener* HpackDecoderNoOpListener::NoOpListener() { + static HpackDecoderNoOpListener* static_instance = + new HpackDecoderNoOpListener(); + return static_instance; +} + +} // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder_listener.h b/http2/hpack/decoder/hpack_decoder_listener.h new file mode 100644 index 0000000..fa68591 --- /dev/null +++ b/http2/hpack/decoder/hpack_decoder_listener.h
@@ -0,0 +1,66 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// 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 "net/third_party/quiche/src/http2/hpack/hpack_string.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { + +class HTTP2_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(HpackEntryType entry_type, + const HpackString& name, + const HpackString& 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(Http2StringPiece error_message) = 0; +}; + +// A no-op implementation of HpackDecoderListener, useful for ignoring +// callbacks once an error is detected. +class HTTP2_EXPORT_PRIVATE HpackDecoderNoOpListener + : public HpackDecoderListener { + public: + HpackDecoderNoOpListener(); + ~HpackDecoderNoOpListener() override; + + void OnHeaderListStart() override; + void OnHeader(HpackEntryType entry_type, + const HpackString& name, + const HpackString& value) override; + void OnHeaderListEnd() override; + void OnHeaderErrorDetected(Http2StringPiece 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/http2/hpack/decoder/hpack_decoder_state.cc b/http2/hpack/decoder/hpack_decoder_state.cc new file mode 100644 index 0000000..2ddf421 --- /dev/null +++ b/http2/hpack/decoder/hpack_decoder_state.cc
@@ -0,0 +1,218 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/hpack/hpack_string.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h" + +namespace http2 { +namespace { + +HpackString ExtractHpackString(HpackDecoderStringBuffer* string_buffer) { + if (string_buffer->IsBuffered()) { + return HpackString(string_buffer->ReleaseString()); + } else { + auto result = HpackString(string_buffer->str()); + string_buffer->Reset(); + return result; + } +} + +} // namespace + +HpackDecoderState::HpackDecoderState(HpackDecoderListener* listener) + : listener_(HTTP2_DIE_IF_NULL(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_detected_(false) {} +HpackDecoderState::~HpackDecoderState() = default; + +void HpackDecoderState::set_tables_debug_listener( + HpackDecoderTablesDebugListener* debug_listener) { + decoder_tables_.set_debug_listener(debug_listener); +} + +void HpackDecoderState::ApplyHeaderTableSizeSetting( + uint32_t header_table_size) { + DVLOG(2) << "HpackDecoderState::ApplyHeaderTableSizeSetting(" + << header_table_size << ")"; + 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; + DVLOG(2) << "low water mark: " << lowest_header_table_size_; + 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() { + 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. + DCHECK(!error_detected_); + 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()); + DVLOG(2) << "HpackDecoderState::OnHeaderListStart " + << "require_dynamic_table_size_update_=" + << require_dynamic_table_size_update_; + listener_->OnHeaderListStart(); +} + +void HpackDecoderState::OnIndexedHeader(size_t index) { + DVLOG(2) << "HpackDecoderState::OnIndexedHeader: " << index; + if (error_detected_) { + return; + } + if (require_dynamic_table_size_update_) { + ReportError("Missing dynamic table size update."); + return; + } + allow_dynamic_table_size_update_ = false; + const HpackStringPair* entry = decoder_tables_.Lookup(index); + if (entry != nullptr) { + listener_->OnHeader(HpackEntryType::kIndexedHeader, entry->name, + entry->value); + } else { + ReportError("Invalid index."); + } +} + +void HpackDecoderState::OnNameIndexAndLiteralValue( + HpackEntryType entry_type, + size_t name_index, + HpackDecoderStringBuffer* value_buffer) { + DVLOG(2) << "HpackDecoderState::OnNameIndexAndLiteralValue " << entry_type + << ", " << name_index << ", " << value_buffer->str(); + if (error_detected_) { + return; + } + if (require_dynamic_table_size_update_) { + ReportError("Missing dynamic table size update."); + return; + } + allow_dynamic_table_size_update_ = false; + const HpackStringPair* entry = decoder_tables_.Lookup(name_index); + if (entry != nullptr) { + HpackString value(ExtractHpackString(value_buffer)); + listener_->OnHeader(entry_type, entry->name, value); + if (entry_type == HpackEntryType::kIndexedLiteralHeader) { + decoder_tables_.Insert(entry->name, value); + } + } else { + ReportError("Invalid name index."); + } +} + +void HpackDecoderState::OnLiteralNameAndValue( + HpackEntryType entry_type, + HpackDecoderStringBuffer* name_buffer, + HpackDecoderStringBuffer* value_buffer) { + DVLOG(2) << "HpackDecoderState::OnLiteralNameAndValue " << entry_type << ", " + << name_buffer->str() << ", " << value_buffer->str(); + if (error_detected_) { + return; + } + if (require_dynamic_table_size_update_) { + ReportError("Missing dynamic table size update."); + return; + } + allow_dynamic_table_size_update_ = false; + HpackString name(ExtractHpackString(name_buffer)); + HpackString value(ExtractHpackString(value_buffer)); + listener_->OnHeader(entry_type, name, value); + if (entry_type == HpackEntryType::kIndexedLiteralHeader) { + decoder_tables_.Insert(name, value); + } +} + +void HpackDecoderState::OnDynamicTableSizeUpdate(size_t size_limit) { + DVLOG(2) << "HpackDecoderState::OnDynamicTableSizeUpdate " << size_limit + << ", required=" + << (require_dynamic_table_size_update_ ? "true" : "false") + << ", allowed=" + << (allow_dynamic_table_size_update_ ? "true" : "false"); + if (error_detected_) { + return; + } + 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("Dynamic table size update not allowed."); + 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("Initial dynamic table size update is above low water mark."); + 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("Dynamic table size update is above acknowledged setting."); + 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(Http2StringPiece error_message) { + DVLOG(2) << "HpackDecoderState::OnHpackDecodeError " << error_message; + if (!error_detected_) { + ReportError(error_message); + } +} + +void HpackDecoderState::OnHeaderBlockEnd() { + DVLOG(2) << "HpackDecoderState::OnHeaderBlockEnd"; + if (error_detected_) { + 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("Missing dynamic table size update."); + } else { + listener_->OnHeaderListEnd(); + } +} + +void HpackDecoderState::ReportError(Http2StringPiece error_message) { + DVLOG(2) << "HpackDecoderState::ReportError is new=" + << (!error_detected_ ? "true" : "false") + << ", error_message: " << error_message; + if (!error_detected_) { + listener_->OnHeaderErrorDetected(error_message); + error_detected_ = true; + } +} + +} // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder_state.h b/http2/hpack/decoder/hpack_decoder_state.h new file mode 100644 index 0000000..fd34179 --- /dev/null +++ b/http2/hpack/decoder/hpack_decoder_state.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. + +// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { +namespace test { +class HpackDecoderStatePeer; +} // namespace test + +class HTTP2_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_; } + + // Set listener to be notified of insertions into the HPACK dynamic table, + // and uses of those entries. + void set_tables_debug_listener( + HpackDecoderTablesDebugListener* debug_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); + + // 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(Http2StringPiece error_message) override; + + // OnHeaderBlockEnd notifies this object that an entire HPACK block has been + // decoded, which might have extended into CONTINUATION blocks. + void OnHeaderBlockEnd(); + + // 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_; } + + const HpackDecoderTables& decoder_tables_for_test() const { + return decoder_tables_; + } + + private: + friend class test::HpackDecoderStatePeer; + + // Reports an error to the listener IF this is the first error detected. + void ReportError(Http2StringPiece error_message); + + // 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? + bool error_detected_; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STATE_H_
diff --git a/http2/hpack/decoder/hpack_decoder_state_test.cc b/http2/hpack/decoder/hpack_decoder_state_test.cc new file mode 100644 index 0000000..115827e --- /dev/null +++ b/http2/hpack/decoder/hpack_decoder_state_test.cc
@@ -0,0 +1,539 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h" + +// Tests of HpackDecoderState. + +#include <utility> +#include <vector> + +#include "base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/hpack_string.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" + +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using ::testing::Eq; +using ::testing::HasSubstr; +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_METHOD0(OnHeaderListStart, void()); + MOCK_METHOD3(OnHeader, + void(HpackEntryType entry_type, + const HpackString& name, + const HpackString& value)); + MOCK_METHOD0(OnHeaderListEnd, void()); + MOCK_METHOD1(OnHeaderErrorDetected, void(Http2StringPiece error_message)); +}; + +enum StringBacking { STATIC, UNBUFFERED, BUFFERED }; + +class HpackDecoderStateTest : public ::testing::Test { + 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(expected_type, 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(entry_type, 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(entry_type, 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.ToStringPiece(), name); + VERIFY_EQ(entry->value.ToStringPiece(), 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(HasSubstr("size update not allowed"))); + SendSizeUpdate(0); +} + +// Confirm that required size updates are indeed required before headers. +TEST_F(HpackDecoderStateTest, RequiredTableSizeChangeBeforeHeader) { + decoder_state_.ApplyHeaderTableSizeSetting(1024); + decoder_state_.ApplyHeaderTableSizeSetting(2048); + + // 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); + SendStartAndVerifyCallback(); + EXPECT_CALL(listener_, OnHeaderErrorDetected( + HasSubstr("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("NOT FORWARDED"); +} + +// 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(HasSubstr("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( + HasSubstr("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(HasSubstr("size update is above"))); + SendSizeUpdate(Http2SettingsInfo::DefaultHeaderTableSize() + 1); +} + +TEST_F(HpackDecoderStateTest, InvalidStaticIndex) { + SendStartAndVerifyCallback(); + EXPECT_CALL(listener_, OnHeaderErrorDetected(HasSubstr("Invalid index"))); + decoder_state_.OnIndexedHeader(0); +} + +TEST_F(HpackDecoderStateTest, InvalidDynamicIndex) { + SendStartAndVerifyCallback(); + EXPECT_CALL(listener_, OnHeaderErrorDetected(HasSubstr("Invalid index"))); + decoder_state_.OnIndexedHeader(kFirstDynamicTableIndex); +} + +TEST_F(HpackDecoderStateTest, InvalidNameIndex) { + SendStartAndVerifyCallback(); + EXPECT_CALL(listener_, + OnHeaderErrorDetected(HasSubstr("Invalid name index"))); + SetValue("value", UNBUFFERED); + decoder_state_.OnNameIndexAndLiteralValue( + HpackEntryType::kIndexedLiteralHeader, kFirstDynamicTableIndex, + &value_buffer_); +} + +TEST_F(HpackDecoderStateTest, ErrorsSuppressCallbacks) { + SendStartAndVerifyCallback(); + EXPECT_CALL(listener_, + OnHeaderErrorDetected(Http2StringPiece("Huffman decode error."))); + decoder_state_.OnHpackDecodeError("Huffman decode error."); + + // 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("NOT FORWARDED"); +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder_string_buffer.cc b/http2/hpack/decoder/hpack_decoder_string_buffer.cc new file mode 100644 index 0000000..b20c37a --- /dev/null +++ b/http2/hpack/decoder/hpack_decoder_string_buffer.cc
@@ -0,0 +1,235 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h" + +#include <utility> + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.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 << "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 << "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() { + DVLOG(3) << "HpackDecoderStringBuffer::Reset"; + state_ = State::RESET; +} + +void HpackDecoderStringBuffer::Set(Http2StringPiece value, bool is_static) { + DVLOG(2) << "HpackDecoderStringBuffer::Set"; + 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) { + DVLOG(2) << "HpackDecoderStringBuffer::OnStart"; + 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_ = Http2StringPiece(); + } +} + +bool HpackDecoderStringBuffer::OnData(const char* data, size_t len) { + DVLOG(2) << "HpackDecoderStringBuffer::OnData state=" << state_ + << ", backing=" << backing_; + DCHECK_EQ(state_, State::COLLECTING); + DCHECK_LE(len, remaining_len_); + remaining_len_ -= len; + + if (is_huffman_encoded_) { + DCHECK_EQ(backing_, Backing::BUFFERED); + return decoder_.Decode(Http2StringPiece(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_ = Http2StringPiece(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. + DCHECK_EQ(backing_, Backing::BUFFERED); + + // Append to the current contents of the buffer. + buffer_.append(data, len); + return true; +} + +bool HpackDecoderStringBuffer::OnEnd() { + DVLOG(2) << "HpackDecoderStringBuffer::OnEnd"; + DCHECK_EQ(state_, State::COLLECTING); + DCHECK_EQ(0u, remaining_len_); + + if (is_huffman_encoded_) { + 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() { + DVLOG(3) << "HpackDecoderStringBuffer::BufferStringIfUnbuffered state=" + << state_ << ", backing=" << backing_; + if (state_ != State::RESET && backing_ == Backing::UNBUFFERED) { + DVLOG(2) << "HpackDecoderStringBuffer buffering Http2String of length " + << value_.size(); + buffer_.assign(value_.data(), value_.size()); + if (state_ == State::COMPLETE) { + value_ = buffer_; + } + backing_ = Backing::BUFFERED; + } +} + +bool HpackDecoderStringBuffer::IsBuffered() const { + DVLOG(3) << "HpackDecoderStringBuffer::IsBuffered"; + return state_ != State::RESET && backing_ == Backing::BUFFERED; +} + +size_t HpackDecoderStringBuffer::BufferedLength() const { + DVLOG(3) << "HpackDecoderStringBuffer::BufferedLength"; + return IsBuffered() ? buffer_.size() : 0; +} + +Http2StringPiece HpackDecoderStringBuffer::str() const { + DVLOG(3) << "HpackDecoderStringBuffer::str"; + DCHECK_EQ(state_, State::COMPLETE); + return value_; +} + +Http2String HpackDecoderStringBuffer::ReleaseString() { + DVLOG(3) << "HpackDecoderStringBuffer::ReleaseString"; + DCHECK_EQ(state_, State::COMPLETE); + DCHECK_EQ(backing_, Backing::BUFFERED); + if (state_ == State::COMPLETE) { + state_ = State::RESET; + if (backing_ == Backing::BUFFERED) { + return std::move(buffer_); + } else { + return Http2String(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 << "}"; +} + +size_t HpackDecoderStringBuffer::EstimateMemoryUsage() const { + return Http2EstimateMemoryUsage(buffer_); +} + +std::ostream& operator<<(std::ostream& out, const HpackDecoderStringBuffer& v) { + v.OutputDebugStringTo(out); + return out; +} + +} // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder_string_buffer.h b/http2/hpack/decoder/hpack_decoder_string_buffer.h new file mode 100644 index 0000000..8a810b2 --- /dev/null +++ b/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 "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { + +class HTTP2_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(Http2StringPiece 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 Http2StringPiece 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). + Http2StringPiece str() 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. + Http2String ReleaseString(); + + State state_for_testing() const { return state_; } + Backing backing_for_testing() const { return backing_; } + void OutputDebugStringTo(std::ostream& out) const; + + // Returns the estimate of dynamically allocated memory in bytes. + size_t EstimateMemoryUsage() const; + + private: + // Storage for the string being buffered, if buffering is necessary + // (e.g. if Huffman encoded, buffer_ is storage for the decoded string). + Http2String buffer_; + + // The Http2StringPiece to be returned by HpackDecoderStringBuffer::str(). If + // a string has been collected, but not buffered, value_ points to that + // string. + Http2StringPiece 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_; +}; + +HTTP2_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/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc b/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc new file mode 100644 index 0000000..009b894 --- /dev/null +++ b/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc
@@ -0,0 +1,251 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h" + +// Tests of HpackDecoderStringBuffer. + +#include <initializer_list> + +#include "base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" + +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using ::testing::HasSubstr; + +namespace http2 { +namespace test { +namespace { + +class HpackDecoderStringBufferTest : public ::testing::Test { + 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 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<Http2String> strs) { + VLOG(1) << buf_; + std::ostringstream ss; + buf_.OutputDebugStringTo(ss); + Http2String dbg_str(ss.str()); + for (const auto& expected : strs) { + VERIFY_THAT(dbg_str, HasSubstr(expected)); + } + return AssertionSuccess(); + } + + HpackDecoderStringBuffer buf_; +}; + +TEST_F(HpackDecoderStringBufferTest, SetStatic) { + Http2StringPiece data("static string"); + + EXPECT_EQ(state(), State::RESET); + EXPECT_TRUE(VerifyLogHasSubstrs({"state=RESET"})); + + buf_.Set(data, /*is_static*/ true); + 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) { + Http2StringPiece data("some text."); + + 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); + 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 Http2StringPiece'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(); + 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) { + Http2StringPiece data("some text."); + Http2StringPiece part1 = data.substr(0, 1); + Http2StringPiece 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()); + 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()); + LOG(INFO) << buf_; + + Http2StringPiece 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) { + Http2String encoded = Http2HexDecode("f1e3c2e5f23a6ba0ab90f4ff"); + Http2StringPiece 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}"})); + + Http2String s = buf_.ReleaseString(); + EXPECT_EQ(s, decoded); + EXPECT_EQ(state(), State::RESET); +} + +TEST_F(HpackDecoderStringBufferTest, HuffmanSplit) { + Http2String encoded = Http2HexDecode("f1e3c2e5f23a6ba0ab90f4ff"); + Http2String part1 = encoded.substr(0, 5); + Http2String part2 = encoded.substr(5); + Http2StringPiece 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()); + 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()); + 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()); + 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()); + LOG(INFO) << buf_; + + buf_.Reset(); + EXPECT_EQ(state(), State::RESET); + LOG(INFO) << buf_; +} + +TEST_F(HpackDecoderStringBufferTest, InvalidHuffmanOnData) { + // Explicitly encode the End-of-String symbol, a no-no. + Http2String encoded = Http2HexDecode("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); + + LOG(INFO) << buf_; +} + +TEST_F(HpackDecoderStringBufferTest, InvalidHuffmanOnEnd) { + // Last byte of string doesn't end with prefix of End-of-String symbol. + Http2String encoded = Http2HexDecode("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()); + LOG(INFO) << buf_; +} + +// TODO(jamessynge): Add tests for ReleaseString(). + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder_tables.cc b/http2/hpack/decoder/hpack_decoder_tables.cc new file mode 100644 index 0000000..5002ab1 --- /dev/null +++ b/http2/hpack/decoder/hpack_decoder_tables.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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.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) \ + DCHECK_EQ(ptr->size(), index); \ + ptr->emplace_back(name, value) + +#include "net/third_party/quiche/src/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 + +HpackDecoderTablesDebugListener::HpackDecoderTablesDebugListener() = default; +HpackDecoderTablesDebugListener::~HpackDecoderTablesDebugListener() = default; + +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::HpackDecoderTableEntry::HpackDecoderTableEntry( + const HpackString& name, + const HpackString& value) + : HpackStringPair(name, value) {} + +HpackDecoderDynamicTable::HpackDecoderDynamicTable() + : insert_count_(kFirstDynamicTableIndex - 1), debug_listener_(nullptr) {} +HpackDecoderDynamicTable::~HpackDecoderDynamicTable() = default; + +void HpackDecoderDynamicTable::DynamicTableSizeUpdate(size_t size_limit) { + DVLOG(3) << "HpackDecoderDynamicTable::DynamicTableSizeUpdate " << size_limit; + EnsureSizeNoMoreThan(size_limit); + 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.). +bool HpackDecoderDynamicTable::Insert(const HpackString& name, + const HpackString& value) { + HpackDecoderTableEntry entry(name, value); + size_t entry_size = entry.size(); + DVLOG(2) << "InsertEntry of size=" << entry_size << "\n name: " << name + << "\n value: " << value; + if (entry_size > size_limit_) { + DVLOG(2) << "InsertEntry: entry larger than table, removing " + << table_.size() << " entries, of total size " << current_size_ + << " bytes."; + table_.clear(); + current_size_ = 0; + return false; // Not inserted because too large. + } + ++insert_count_; + if (debug_listener_ != nullptr) { + entry.time_added = debug_listener_->OnEntryInserted(entry, insert_count_); + DVLOG(2) << "OnEntryInserted returned time_added=" << entry.time_added + << " for insert_count_=" << insert_count_; + } + size_t insert_limit = size_limit_ - entry_size; + EnsureSizeNoMoreThan(insert_limit); + table_.push_front(entry); + current_size_ += entry_size; + DVLOG(2) << "InsertEntry: current_size_=" << current_size_; + DCHECK_GE(current_size_, entry_size); + DCHECK_LE(current_size_, size_limit_); + return true; +} + +const HpackStringPair* HpackDecoderDynamicTable::Lookup(size_t index) const { + if (index < table_.size()) { + const HpackDecoderTableEntry& entry = table_[index]; + if (debug_listener_ != nullptr) { + size_t insert_count_of_index = insert_count_ + table_.size() - index; + debug_listener_->OnUseEntry(entry, insert_count_of_index, + entry.time_added); + } + return &entry; + } + return nullptr; +} + +void HpackDecoderDynamicTable::EnsureSizeNoMoreThan(size_t limit) { + 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(); + } + DCHECK_LE(current_size_, limit); +} + +void HpackDecoderDynamicTable::RemoveLastEntry() { + DCHECK(!table_.empty()); + if (!table_.empty()) { + DVLOG(2) << "RemoveLastEntry current_size_=" << current_size_ + << ", last entry size=" << table_.back().size(); + DCHECK_GE(current_size_, table_.back().size()); + current_size_ -= table_.back().size(); + table_.pop_back(); + // Empty IFF current_size_ == 0. + DCHECK_EQ(table_.empty(), current_size_ == 0); + } +} + +HpackDecoderTables::HpackDecoderTables() = default; +HpackDecoderTables::~HpackDecoderTables() = default; + +void HpackDecoderTables::set_debug_listener( + HpackDecoderTablesDebugListener* debug_listener) { + dynamic_table_.set_debug_listener(debug_listener); +} + +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/http2/hpack/decoder/hpack_decoder_tables.h b/http2/hpack/decoder/hpack_decoder_tables.h new file mode 100644 index 0000000..6df145c --- /dev/null +++ b/http2/hpack/decoder/hpack_decoder_tables.h
@@ -0,0 +1,197 @@ +// 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 <vector> + +#include "net/third_party/quiche/src/http2/hpack/hpack_string.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_containers.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" + +namespace http2 { +namespace test { +class HpackDecoderTablesPeer; +} // namespace test + +// HpackDecoderTablesDebugListener supports a QUIC experiment, enabling +// the gathering of information about the time-line of use of HPACK +// dynamic table entries. +class HTTP2_EXPORT_PRIVATE HpackDecoderTablesDebugListener { + public: + HpackDecoderTablesDebugListener(); + virtual ~HpackDecoderTablesDebugListener(); + + HpackDecoderTablesDebugListener(const HpackDecoderTablesDebugListener&) = + delete; + HpackDecoderTablesDebugListener& operator=( + const HpackDecoderTablesDebugListener&) = delete; + + // The entry has been inserted into the dynamic table. insert_count starts at + // 62 because 61 is the last index in the static table; insert_count increases + // by 1 with each insert into the dynamic table; it is not incremented when + // when a entry is too large to fit into the dynamic table at all (which has + // the effect of emptying the dynamic table). + // Returns a value that can be used as time_added in OnUseEntry. + virtual int64_t OnEntryInserted(const HpackStringPair& entry, + size_t insert_count) = 0; + + // The entry has been used, either for the name or for the name and value. + // insert_count is the same as passed to OnEntryInserted when entry was + // inserted to the dynamic table, and time_added is the value that was + // returned by OnEntryInserted. + virtual void OnUseEntry(const HpackStringPair& entry, + size_t insert_count, + int64_t time_added) = 0; +}; + +// 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 HTTP2_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 HTTP2_EXPORT_PRIVATE HpackDecoderDynamicTable { + public: + HpackDecoderDynamicTable(); + ~HpackDecoderDynamicTable(); + + HpackDecoderDynamicTable(const HpackDecoderDynamicTable&) = delete; + HpackDecoderDynamicTable& operator=(const HpackDecoderDynamicTable&) = delete; + + // Set the listener to be notified of insertions into this table, and later + // uses of those entries. Added for evaluation of changes to QUIC's use + // of HPACK. + void set_debug_listener(HpackDecoderTablesDebugListener* debug_listener) { + debug_listener_ = debug_listener; + } + + // 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); + + // Returns true if inserted, false if too large (at which point the + // dynamic table will be empty.) + bool Insert(const HpackString& name, const HpackString& 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; + struct HpackDecoderTableEntry : public HpackStringPair { + HpackDecoderTableEntry(const HpackString& name, const HpackString& value); + int64_t time_added; + }; + + // 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(); + + Http2Deque<HpackDecoderTableEntry> 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_; + HpackDecoderTablesDebugListener* debug_listener_; +}; + +class HTTP2_EXPORT_PRIVATE HpackDecoderTables { + public: + HpackDecoderTables(); + ~HpackDecoderTables(); + + HpackDecoderTables(const HpackDecoderTables&) = delete; + HpackDecoderTables& operator=(const HpackDecoderTables&) = delete; + + // Set the listener to be notified of insertions into the dynamic table, and + // later uses of those entries. Added for evaluation of changes to QUIC's use + // of HPACK. + void set_debug_listener(HpackDecoderTablesDebugListener* debug_listener); + + // 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); + } + + // Returns true if inserted, false if too large (at which point the + // dynamic table will be empty.) + // TODO(jamessynge): Add methods for moving the string(s) into the table, + // or for otherwise avoiding unnecessary copies. + bool Insert(const HpackString& name, const HpackString& value) { + return dynamic_table_.Insert(name, 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/http2/hpack/decoder/hpack_decoder_tables_test.cc b/http2/hpack/decoder/hpack_decoder_tables_test.cc new file mode 100644 index 0000000..370c1a5 --- /dev/null +++ b/http2/hpack/decoder/hpack_decoder_tables_test.cc
@@ -0,0 +1,266 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h" + +#include <algorithm> +#include <tuple> +#include <vector> + +#include "base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/random_util.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) \ + DCHECK_EQ(static_entries.size() + 1, index); \ + static_entries.push_back({name, value, index}); + +#include "net/third_party/quiche/src/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 ::testing::Test { + 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 Http2String& name, const Http2String& 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<Http2String, Http2String, size_t> FakeHpackEntry; +const Http2String& Name(const FakeHpackEntry& entry) { + return std::get<0>(entry); +} +const Http2String& 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 Http2String& name, const Http2String& 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 Http2String& name, const Http2String& value) { + size_t old_count = num_dynamic_entries(); + if (tables_.Insert(HpackString(name), HpackString(value))) { + VERIFY_GT(current_dynamic_size(), 0u); + VERIFY_GT(num_dynamic_entries(), 0u); + } else { + VERIFY_EQ(current_dynamic_size(), 0u); + VERIFY_EQ(num_dynamic_entries(), 0u); + } + 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) { + Http2String name = + GenerateHttp2HeaderName(random_.UniformInRange(2, 40), RandomPtr()); + Http2String value = + GenerateWebSafeString(random_.UniformInRange(2, 600), RandomPtr()); + ASSERT_TRUE(Insert(name, value)); + } + EXPECT_TRUE(VerifyStaticTableContents()); + } +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/hpack/decoder/hpack_decoder_test.cc b/http2/hpack/decoder/hpack_decoder_test.cc new file mode 100644 index 0000000..563e977 --- /dev/null +++ b/http2/hpack/decoder/hpack_decoder_test.cc
@@ -0,0 +1,1219 @@ +// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h" + +// Tests of HpackDecoder. + +#include <tuple> +#include <utility> +#include <vector> + +#include "base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h" +#include "net/third_party/quiche/src/http2/hpack/hpack_string.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h" +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_example.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/random_util.h" + +using ::testing::AssertionFailure; +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using ::testing::ElementsAreArray; +using ::testing::HasSubstr; + +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::tuple<HpackEntryType, Http2String, Http2String> 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_METHOD0(OnHeaderListStart, void()); + MOCK_METHOD3(OnHeader, + void(HpackEntryType entry_type, + const HpackString& name, + const HpackString& value)); + MOCK_METHOD0(OnHeaderListEnd, void()); + MOCK_METHOD1(OnHeaderErrorDetected, void(Http2StringPiece error_message)); +}; + +class HpackDecoderTest : public ::testing::TestWithParam<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(HpackEntryType entry_type, + const HpackString& name, + const HpackString& value) override { + ASSERT_TRUE(saw_start_); + ASSERT_FALSE(saw_end_); + // header_entries_.push_back({entry_type, name.ToString(), + // value.ToString()}); + header_entries_.emplace_back(entry_type, name.ToString(), value.ToString()); + } + + // 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(Http2StringPiece error_message) override { + ASSERT_TRUE(saw_start_); + error_messages_.push_back(Http2String(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(Http2StringPiece block) { + VLOG(1) << "HpackDecoderTest::DecodeBlock"; + + VERIFY_FALSE(decoder_.error_detected()); + VERIFY_TRUE(error_messages_.empty()); + VERIFY_FALSE(saw_start_); + VERIFY_FALSE(saw_end_); + header_entries_.clear(); + + VERIFY_FALSE(decoder_.error_detected()); + VERIFY_TRUE(decoder_.StartDecodingBlock()); + VERIFY_FALSE(decoder_.error_detected()); + + 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_.error_detected()); + + VERIFY_TRUE(decoder_.EndDecodingBlock()); + if (saw_end_) { + VERIFY_FALSE(decoder_.error_detected()); + VERIFY_TRUE(error_messages_.empty()); + } else { + VERIFY_TRUE(decoder_.error_detected()); + 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.ToStringPiece(), name); + VERIFY_EQ(entry->value.ToStringPiece(), 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<Http2String> error_messages_; + bool fragment_the_hpack_block_; + bool saw_start_ = false; + bool saw_end_ = false; +}; +INSTANTIATE_TEST_CASE_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 + Http2String 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{HpackEntryType::kIndexedHeader, ":method", "GET"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "http"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", "/"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, ":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{HpackEntryType::kIndexedHeader, ":method", "GET"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "http"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", "/"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":authority", + "www.example.com"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + "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{HpackEntryType::kIndexedHeader, ":method", "GET"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "https"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", + "/index.html"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":authority", + "www.example.com"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, "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 + Http2String 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{HpackEntryType::kIndexedHeader, ":method", "GET"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "http"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", "/"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, ":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{HpackEntryType::kIndexedHeader, ":method", "GET"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "http"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", "/"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":authority", + "www.example.com"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + "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{HpackEntryType::kIndexedHeader, ":method", "GET"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "https"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", + "/index.html"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":authority", + "www.example.com"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, "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 + + Http2String 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{HpackEntryType::kIndexedLiteralHeader, + ":status", "302"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + "cache-control", "private"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + "date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + "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{HpackEntryType::kIndexedLiteralHeader, + ":status", "307"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, + "cache-control", "private"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, "date", + "Mon, 21 Oct 2013 20:13:21 GMT"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, "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{HpackEntryType::kIndexedHeader, ":status", "200"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, "cache-control", + "private"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, "date", + "Mon, 21 Oct 2013 20:13:22 GMT"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, "location", + "https://www.example.com"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + "content-encoding", "gzip"}, + HpackHeaderEntry{ + HpackEntryType::kIndexedLiteralHeader, "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 + Http2String 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{HpackEntryType::kIndexedLiteralHeader, + ":status", "302"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + "cache-control", "private"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + "date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + "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{HpackEntryType::kIndexedLiteralHeader, + ":status", "307"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, + "cache-control", "private"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, "date", + "Mon, 21 Oct 2013 20:13:21 GMT"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, "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{HpackEntryType::kIndexedHeader, ":status", "200"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, "cache-control", + "private"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, "date", + "Mon, 21 Oct 2013 20:13:22 GMT"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, "location", + "https://www.example.com"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + "content-encoding", "gzip"}, + HpackHeaderEntry{ + HpackEntryType::kIndexedLiteralHeader, "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(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], HasSubstr("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) { + // 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()); + { + HpackBlockBuilder hbb; + hbb.AppendDynamicTableSizeUpdate(1024); + hbb.AppendIndexedHeader(4); // :path: / + EXPECT_TRUE(DecodeBlock(hbb.buffer())); + EXPECT_THAT(header_entries_, + ElementsAreArray({HpackHeaderEntry{ + HpackEntryType::kIndexedHeader, ":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); + { + 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{ + HpackEntryType::kIndexedHeader, ":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); + { + 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(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], HasSubstr("size update not allowed")); + EXPECT_EQ(700u, header_table_size_limit()); + EXPECT_EQ(0u, current_header_table_size()); + EXPECT_TRUE(header_entries_.empty()); + } + // 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(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], HasSubstr("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(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], + HasSubstr("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(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], + HasSubstr("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(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], + HasSubstr("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(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], + HasSubstr("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_.error_detected()); + EXPECT_FALSE(saw_end_); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], HasSubstr("malformed")); + 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_.error_detected()); + EXPECT_FALSE(saw_end_); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], HasSubstr("Invalid index")); + 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(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], HasSubstr("truncated")); + // 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{HpackEntryType::kNeverIndexedLiteralHeader, + "name", "some data."}, + HpackHeaderEntry{HpackEntryType::kUnindexedLiteralHeader, + "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{ + HpackEntryType::kNeverIndexedLiteralHeader, "name", "some data."}})); + EXPECT_FALSE(saw_end_); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], HasSubstr("too long")); +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/hpack/decoder/hpack_entry_collector.cc b/http2/hpack/decoder/hpack_entry_collector.cc new file mode 100644 index 0000000..9d17465 --- /dev/null +++ b/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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h" + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_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 Http2String& value) + : header_type_(type), + index_(index), + value_(value, value_huffman), + started_(true), + ended_(true) {} +HpackEntryCollector::HpackEntryCollector(HpackEntryType type, + bool name_huffman, + const Http2String& name, + bool value_huffman, + const Http2String& 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, + Http2StringPiece 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, + Http2StringPiece expected_name, + bool expected_value_huffman, + Http2StringPiece 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) { + CHECK(name_.IsClear()); + hbb->AppendNameIndexAndLiteralValue(header_type_, index_, + value_.huffman_encoded, value_.s); + } else { + CHECK(name_.HasEnded()) << *this; + hbb->AppendLiteralNameAndValue(header_type_, name_.huffman_encoded, + name_.s, value_.huffman_encoded, + value_.s); + } + return; + + default: + ADD_FAILURE() << *this; + } +} + +Http2String HpackEntryCollector::ToString() const { + Http2String 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 { + Http2StrAppend(&result, header_type_); + } + } + if (index_ != 0) { + Http2StrAppend(&result, " Index=", index_); + } + if (!name_.IsClear()) { + Http2StrAppend(&result, " Name", name_.ToString()); + } + if (!value_.IsClear()) { + Http2StrAppend(&result, " Value", value_.ToString()); + } + if (!started_) { + EXPECT_FALSE(ended_); + Http2StrAppend(&result, " !started"); + } else if (!ended_) { + Http2StrAppend(&result, " !ended"); + } else { + Http2StrAppend(&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/http2/hpack/decoder/hpack_entry_collector.h b/http2/hpack/decoder/hpack_entry_collector.h new file mode 100644 index 0000000..c2c5fe5 --- /dev/null +++ b/http2/hpack/decoder/hpack_entry_collector.h
@@ -0,0 +1,155 @@ +// 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 "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { +namespace test { + +class 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 Http2String& value); + HpackEntryCollector(HpackEntryType type, + bool name_huffman, + const Http2String& name, + bool value_huffman, + const Http2String& 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, + Http2StringPiece 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, + Http2StringPiece expected_name, + bool expected_value_huffman, + Http2StringPiece 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. + Http2String 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; +}; + +bool operator==(const HpackEntryCollector& a, const HpackEntryCollector& b); +bool operator!=(const HpackEntryCollector& a, const HpackEntryCollector& b); +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/http2/hpack/decoder/hpack_entry_decoder.cc b/http2/hpack/decoder/hpack_entry_decoder.cc new file mode 100644 index 0000000..89562a9 --- /dev/null +++ b/http2/hpack/decoder/hpack_entry_decoder.cc
@@ -0,0 +1,267 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.h" + +#include <stddef.h> + +#include <cstdint> + +#include "base/logging.h" +#include "base/macros.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h" + +namespace http2 { +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) { + DCHECK(db != nullptr); + DCHECK(listener != nullptr); + 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. + DCHECK_EQ(0u, db->Remaining()); + state_ = EntryDecoderState::kResumeDecodingType; + return status; + case DecodeStatus::kDecodeError: + // The varint must have been invalid (too long). + return status; + } + + HTTP2_BUG << "Unreachable"; + return DecodeStatus::kDecodeError; +} + +DecodeStatus HpackEntryDecoder::Resume(DecodeBuffer* db, + HpackEntryDecoderListener* listener) { + DCHECK(db != nullptr); + DCHECK(listener != nullptr); + + DecodeStatus status; + + do { + switch (state_) { + case EntryDecoderState::kResumeDecodingType: + // entry_type_decoder_ returned kDecodeInProgress when last called. + DVLOG(1) << "kResumeDecodingType: db->Remaining=" << db->Remaining(); + status = entry_type_decoder_.Resume(db); + if (status != DecodeStatus::kDecodeDone) { + return status; + } + state_ = EntryDecoderState::kDecodedType; + HTTP2_FALLTHROUGH; + + case EntryDecoderState::kDecodedType: + // entry_type_decoder_ returned kDecodeDone, now need to decide how + // to proceed. + DVLOG(1) << "kDecodedType: db->Remaining=" << db->Remaining(); + if (DispatchOnType(listener)) { + // All done. + return DecodeStatus::kDecodeDone; + } + continue; + + case EntryDecoderState::kStartDecodingName: + 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; + return status; + } + state_ = EntryDecoderState::kStartDecodingValue; + HTTP2_FALLTHROUGH; + + case EntryDecoderState::kStartDecodingValue: + DVLOG(1) << "kStartDecodingValue: db->Remaining=" << db->Remaining(); + { + ValueDecoderListener vcb(listener); + status = string_decoder_.Start(db, &vcb); + } + 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. + 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; + return status; + } + state_ = EntryDecoderState::kStartDecodingValue; + break; + + case EntryDecoderState::kResumeDecodingValue: + // The literal value was split across decode buffers. + DVLOG(1) << "kResumeDecodingValue: db->Remaining=" << db->Remaining(); + { + ValueDecoderListener vcb(listener); + status = string_decoder_.Resume(db, &vcb); + } + 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 = 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 << "Unreachable, entry_type=" << entry_type; + return true; +} + +void HpackEntryDecoder::OutputDebugString(std::ostream& out) const { + out << "HpackEntryDecoder(state=" << state_ << ", " << entry_type_decoder_ + << ", " << string_decoder_ << ")"; +} + +Http2String 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/http2/hpack/decoder/hpack_entry_decoder.h b/http2/hpack/decoder/hpack_entry_decoder.h new file mode 100644 index 0000000..fe59d96 --- /dev/null +++ b/http2/hpack/decoder/hpack_entry_decoder.h
@@ -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. + +#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 "base/logging.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" + +namespace http2 { + +class HTTP2_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); + + Http2String 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(); +}; + +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const HpackEntryDecoder& v); +HTTP2_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/http2/hpack/decoder/hpack_entry_decoder_listener.cc b/http2/hpack/decoder/hpack_entry_decoder_listener.cc new file mode 100644 index 0000000..b783b15 --- /dev/null +++ b/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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h" + +#include "base/logging.h" + +namespace http2 { + +void HpackEntryDecoderVLoggingListener::OnIndexedHeader(size_t index) { + VLOG(1) << "OnIndexedHeader, index=" << index; + if (wrapped_) { + wrapped_->OnIndexedHeader(index); + } +} + +void HpackEntryDecoderVLoggingListener::OnStartLiteralHeader( + HpackEntryType entry_type, + size_t maybe_name_index) { + 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) { + VLOG(1) << "OnNameStart: H=" << huffman_encoded << ", len=" << len; + if (wrapped_) { + wrapped_->OnNameStart(huffman_encoded, len); + } +} + +void HpackEntryDecoderVLoggingListener::OnNameData(const char* data, + size_t len) { + VLOG(1) << "OnNameData: len=" << len; + if (wrapped_) { + wrapped_->OnNameData(data, len); + } +} + +void HpackEntryDecoderVLoggingListener::OnNameEnd() { + VLOG(1) << "OnNameEnd"; + if (wrapped_) { + wrapped_->OnNameEnd(); + } +} + +void HpackEntryDecoderVLoggingListener::OnValueStart(bool huffman_encoded, + size_t len) { + VLOG(1) << "OnValueStart: H=" << huffman_encoded << ", len=" << len; + if (wrapped_) { + wrapped_->OnValueStart(huffman_encoded, len); + } +} + +void HpackEntryDecoderVLoggingListener::OnValueData(const char* data, + size_t len) { + VLOG(1) << "OnValueData: len=" << len; + if (wrapped_) { + wrapped_->OnValueData(data, len); + } +} + +void HpackEntryDecoderVLoggingListener::OnValueEnd() { + VLOG(1) << "OnValueEnd"; + if (wrapped_) { + wrapped_->OnValueEnd(); + } +} + +void HpackEntryDecoderVLoggingListener::OnDynamicTableSizeUpdate(size_t size) { + VLOG(1) << "OnDynamicTableSizeUpdate: size=" << size; + if (wrapped_) { + wrapped_->OnDynamicTableSizeUpdate(size); + } +} + +} // namespace http2
diff --git a/http2/hpack/decoder/hpack_entry_decoder_listener.h b/http2/hpack/decoder/hpack_entry_decoder_listener.h new file mode 100644 index 0000000..fd11f59 --- /dev/null +++ b/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 "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" + +namespace http2 { + +class HTTP2_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 HTTP2_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 HTTP2_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/http2/hpack/decoder/hpack_entry_decoder_test.cc b/http2/hpack/decoder/hpack_entry_decoder_test.cc new file mode 100644 index 0000000..7249bd5 --- /dev/null +++ b/http2/hpack/decoder/hpack_entry_decoder_test.cc
@@ -0,0 +1,211 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.h" + +// Tests of HpackEntryDecoder. + +#include <cstdint> + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h" +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +using ::testing::AssertionResult; + +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]() { + VERIFY_AND_RETURN_SUCCESS(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]() { + VERIFY_AND_RETURN_SUCCESS(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]() { + VERIFY_AND_RETURN_SUCCESS(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]() { + VERIFY_AND_RETURN_SUCCESS(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]() { + VERIFY_AND_RETURN_SUCCESS(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]() { + VERIFY_AND_RETURN_SUCCESS(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]() { + VERIFY_AND_RETURN_SUCCESS(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_CASE_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 Http2String 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 { + VERIFY_AND_RETURN_SUCCESS(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 Http2String name = Random().RandString(name_len); + const bool value_is_huffman_encoded = (n & 2) == 0; + const int value_len = Random().Skewed(10); + const Http2String 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 { + VERIFY_AND_RETURN_SUCCESS(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/http2/hpack/decoder/hpack_entry_type_decoder.cc b/http2/hpack/decoder/hpack_entry_type_decoder.cc new file mode 100644 index 0000000..121e3d0 --- /dev/null +++ b/http2/hpack/decoder/hpack_entry_type_decoder.cc
@@ -0,0 +1,358 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { + +Http2String HpackEntryTypeDecoder::DebugString() const { + return Http2StrCat( + "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) { + DCHECK(db != nullptr); + 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 << "Unreachable, byte=" << std::hex << static_cast<uint32_t>(byte); + return DecodeStatus::kDecodeError; +} + +} // namespace http2
diff --git a/http2/hpack/decoder/hpack_entry_type_decoder.h b/http2/hpack/decoder/hpack_entry_type_decoder.h new file mode 100644 index 0000000..1c0f2ac --- /dev/null +++ b/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 "base/logging.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" + +namespace http2 { + +class HTTP2_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. + uint32_t varint() const { return varint_decoder_.value(); } + + Http2String 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; +}; + +HTTP2_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/http2/hpack/decoder/hpack_entry_type_decoder_test.cc b/http2/hpack/decoder/hpack_entry_type_decoder_test.cc new file mode 100644 index 0000000..afff7cc --- /dev/null +++ b/http2/hpack/decoder/hpack_entry_type_decoder_test.cc
@@ -0,0 +1,87 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.h" + +#include <vector> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +using ::testing::AssertionFailure; +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; + +namespace http2 { +namespace test { +namespace { +const bool kReturnNonZeroOnFirst = true; + +class HpackEntryTypeDecoderTest : public RandomDecoderTest { + protected: + DecodeStatus StartDecoding(DecodeBuffer* b) override { + 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/http2/hpack/decoder/hpack_string_collector.cc b/http2/hpack/decoder/hpack_string_collector.cc new file mode 100644 index 0000000..247ce9c --- /dev/null +++ b/http2/hpack/decoder/hpack_string_collector.cc
@@ -0,0 +1,123 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h" + +#include <stddef.h> + +#include <iosfwd> +#include <ostream> + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_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 Http2String& 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) { + Http2StringPiece sp(data, length); + EXPECT_TRUE(IsInProgress()) << ToString(); + EXPECT_LE(sp.size(), len) << ToString(); + Http2StrAppend(&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( + Http2StringPiece 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(); +} + +Http2String 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=\"" << Http2HexEscape(v.s) << "\")"; +} + +} // namespace test +} // namespace http2
diff --git a/http2/hpack/decoder/hpack_string_collector.h b/http2/hpack/decoder/hpack_string_collector.h new file mode 100644 index 0000000..76be13b --- /dev/null +++ b/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 "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +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 Http2String& 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(Http2StringPiece str, + bool is_huffman_encoded) const; + + Http2String ToString() const; + + Http2String 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/http2/hpack/decoder/hpack_string_decoder.cc b/http2/hpack/decoder/hpack_string_decoder.cc new file mode 100644 index 0000000..0b9eb59 --- /dev/null +++ b/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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h" + +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { + +Http2String HpackStringDecoder::DebugString() const { + return Http2StrCat("HpackStringDecoder(state=", StateToString(state_), + ", length=", length_decoder_.DebugString(), + ", remaining=", remaining_, + ", huffman=", huffman_encoded_ ? "true)" : "false)"); +} + +// static +Http2String HpackStringDecoder::StateToString(StringDecoderState v) { + switch (v) { + case kStartDecodingLength: + return "kStartDecodingLength"; + case kDecodingString: + return "kDecodingString"; + case kResumeDecodingLength: + return "kResumeDecodingLength"; + } + return Http2StrCat("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/http2/hpack/decoder/hpack_string_decoder.h b/http2/hpack/decoder/hpack_string_decoder.h new file mode 100644 index 0000000..8ec0169 --- /dev/null +++ b/http2/hpack/decoder/hpack_string_decoder.h
@@ -0,0 +1,208 @@ +// 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 "base/logging.h" +#include "base/macros.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.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 HTTP2_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: + 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. + HTTP2_FALLTHROUGH; + + case kDecodingString: + DVLOG(2) << "kDecodingString: db->Remaining=" << db->Remaining() + << " remaining_=" << remaining_; + return DecodeString(db, cb); + + case kResumeDecodingLength: + DVLOG(2) << "kResumeDecodingLength: db->Remaining=" + << db->Remaining(); + if (!ResumeDecodingLength(db, cb, &status)) { + return status; + } + } + } + } + + Http2String DebugString() const; + + private: + static Http2String 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) { + 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) { + remaining_ = 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; +}; + +HTTP2_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/http2/hpack/decoder/hpack_string_decoder_listener.cc b/http2/hpack/decoder/hpack_string_decoder_listener.cc new file mode 100644 index 0000000..e0fbc65 --- /dev/null +++ b/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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.h" + +#include "base/logging.h" + +namespace http2 { +namespace test { + +void HpackStringDecoderVLoggingListener::OnStringStart(bool huffman_encoded, + size_t len) { + VLOG(1) << "OnStringStart: H=" << huffman_encoded << ", len=" << len; + if (wrapped_) { + wrapped_->OnStringStart(huffman_encoded, len); + } +} + +void HpackStringDecoderVLoggingListener::OnStringData(const char* data, + size_t len) { + VLOG(1) << "OnStringData: len=" << len; + if (wrapped_) { + return wrapped_->OnStringData(data, len); + } +} + +void HpackStringDecoderVLoggingListener::OnStringEnd() { + VLOG(1) << "OnStringEnd"; + if (wrapped_) { + return wrapped_->OnStringEnd(); + } +} + +} // namespace test +} // namespace http2
diff --git a/http2/hpack/decoder/hpack_string_decoder_listener.h b/http2/hpack/decoder/hpack_string_decoder_listener.h new file mode 100644 index 0000000..35a0417 --- /dev/null +++ b/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 "net/third_party/quiche/src/http2/platform/api/http2_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 HTTP2_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 HTTP2_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/http2/hpack/decoder/hpack_string_decoder_test.cc b/http2/hpack/decoder/hpack_string_decoder_test.cc new file mode 100644 index 0000000..349b649 --- /dev/null +++ b/http2/hpack/decoder/hpack_string_decoder_test.cc
@@ -0,0 +1,155 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h" + +// Tests of HpackStringDecoder. + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.h" +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +using ::testing::AssertionResult; + +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. + VLOG(1) << decoder_.DebugString(); + VLOG(2) << collector_; + return decoder_.Resume(b, &listener_); + } + + AssertionResult Collected(Http2StringPiece s, bool huffman_encoded) { + VLOG(1) << collector_; + return collector_.Collected(s, huffman_encoded); + } + + // expected_str is a Http2String rather than a const Http2String& or + // Http2StringPiece 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 Http2String& 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)); + } + VLOG(2) << collector_.ToString(); + collector_.Clear(); + 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)); + Http2StringPiece data("\x0astart end."); + DecodeBuffer b(data); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator)); + } +} + +TEST_F(HpackStringDecoderTest, DecodeLongStrings) { + Http2String name = Random().RandString(1024); + Http2String 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/http2/hpack/decoder/hpack_whole_entry_buffer.cc b/http2/hpack/decoder/hpack_whole_entry_buffer.cc new file mode 100644 index 0000000..b3e29b1 --- /dev/null +++ b/http2/hpack/decoder/hpack_whole_entry_buffer.cc
@@ -0,0 +1,139 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_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) { + listener_ = HTTP2_DIE_IF_NULL(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(); +} + +size_t HpackWholeEntryBuffer::EstimateMemoryUsage() const { + return Http2EstimateMemoryUsage(name_) + Http2EstimateMemoryUsage(value_); +} + +void HpackWholeEntryBuffer::OnIndexedHeader(size_t index) { + DVLOG(2) << "HpackWholeEntryBuffer::OnIndexedHeader: index=" << index; + listener_->OnIndexedHeader(index); +} + +void HpackWholeEntryBuffer::OnStartLiteralHeader(HpackEntryType entry_type, + size_t maybe_name_index) { + 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) { + DVLOG(2) << "HpackWholeEntryBuffer::OnNameStart: huffman_encoded=" + << (huffman_encoded ? "true" : "false") << ", len=" << len; + DCHECK_EQ(maybe_name_index_, 0u); + if (!error_detected_) { + if (len > max_string_size_bytes_) { + DVLOG(1) << "Name length (" << len << ") is longer than permitted (" + << max_string_size_bytes_ << ")"; + ReportError("HPACK entry name size is too long."); + return; + } + name_.OnStart(huffman_encoded, len); + } +} + +void HpackWholeEntryBuffer::OnNameData(const char* data, size_t len) { + DVLOG(2) << "HpackWholeEntryBuffer::OnNameData: len=" << len << " data:\n" + << Http2HexDump(Http2StringPiece(data, len)); + DCHECK_EQ(maybe_name_index_, 0u); + if (!error_detected_ && !name_.OnData(data, len)) { + ReportError("Error decoding HPACK entry name."); + } +} + +void HpackWholeEntryBuffer::OnNameEnd() { + DVLOG(2) << "HpackWholeEntryBuffer::OnNameEnd"; + DCHECK_EQ(maybe_name_index_, 0u); + if (!error_detected_ && !name_.OnEnd()) { + ReportError("Error decoding HPACK entry name."); + } +} + +void HpackWholeEntryBuffer::OnValueStart(bool huffman_encoded, size_t len) { + DVLOG(2) << "HpackWholeEntryBuffer::OnValueStart: huffman_encoded=" + << (huffman_encoded ? "true" : "false") << ", len=" << len; + if (!error_detected_) { + if (len > max_string_size_bytes_) { + DVLOG(1) << "Value length (" << len << ") is longer than permitted (" + << max_string_size_bytes_ << ")"; + ReportError("HPACK entry value size is too long."); + return; + } + value_.OnStart(huffman_encoded, len); + } +} + +void HpackWholeEntryBuffer::OnValueData(const char* data, size_t len) { + DVLOG(2) << "HpackWholeEntryBuffer::OnValueData: len=" << len << " data:\n" + << Http2HexDump(Http2StringPiece(data, len)); + if (!error_detected_ && !value_.OnData(data, len)) { + ReportError("Error decoding HPACK entry value."); + } +} + +void HpackWholeEntryBuffer::OnValueEnd() { + DVLOG(2) << "HpackWholeEntryBuffer::OnValueEnd"; + if (error_detected_) { + return; + } + if (!value_.OnEnd()) { + ReportError("Error decoding HPACK entry value."); + 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) { + DVLOG(2) << "HpackWholeEntryBuffer::OnDynamicTableSizeUpdate: size=" << size; + listener_->OnDynamicTableSizeUpdate(size); +} + +void HpackWholeEntryBuffer::ReportError(Http2StringPiece error_message) { + if (!error_detected_) { + DVLOG(1) << "HpackWholeEntryBuffer::ReportError: " << error_message; + error_detected_ = true; + listener_->OnHpackDecodeError(error_message); + listener_ = HpackWholeEntryNoOpListener::NoOpListener(); + } +} + +} // namespace http2
diff --git a/http2/hpack/decoder/hpack_whole_entry_buffer.h b/http2/hpack/decoder/hpack_whole_entry_buffer.h new file mode 100644 index 0000000..61bf583 --- /dev/null +++ b/http2/hpack/decoder/hpack_whole_entry_buffer.h
@@ -0,0 +1,104 @@ +// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { + +// TODO(jamessynge): Consider renaming HpackEntryDecoderListener to +// HpackEntryPartsListener or HpackEntryFragmentsListener. +class HTTP2_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_; } + + // Returns the estimate of dynamically allocated memory in bytes. + size_t EstimateMemoryUsage() const; + + // 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(Http2StringPiece error_message); + + 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/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc b/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc new file mode 100644 index 0000000..75b281c --- /dev/null +++ b/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc
@@ -0,0 +1,206 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/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 "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::AllOf; +using ::testing::HasSubstr; +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_METHOD1(OnIndexedHeader, void(size_t index)); + MOCK_METHOD3(OnNameIndexAndLiteralValue, + void(HpackEntryType entry_type, + size_t name_index, + HpackDecoderStringBuffer* value_buffer)); + MOCK_METHOD3(OnLiteralNameAndValue, + void(HpackEntryType entry_type, + HpackDecoderStringBuffer* name_buffer, + HpackDecoderStringBuffer* value_buffer)); + MOCK_METHOD1(OnDynamicTableSizeUpdate, void(size_t size)); + MOCK_METHOD1(OnHpackDecodeError, void(Http2StringPiece error_message)); +}; + +class HpackWholeEntryBufferTest : public ::testing::Test { + 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(HasSubstr("HPACK entry name"))); + entry_buffer_.OnNameStart(false, kMaxStringSize + 1); +} + +// Verify that a name longer than the allowed size generates an error. +TEST_F(HpackWholeEntryBufferTest, ValueTooLong) { + entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 1); + EXPECT_CALL(listener_, OnHpackDecodeError(HasSubstr("HPACK entry value"))); + 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(HasSubstr("HPACK entry name"))); + + 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, ValueeHuffmanError) { + 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(HasSubstr("HPACK entry value"))); + + 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/http2/hpack/decoder/hpack_whole_entry_listener.cc b/http2/hpack/decoder/hpack_whole_entry_listener.cc new file mode 100644 index 0000000..b92e64a --- /dev/null +++ b/http2/hpack/decoder/hpack_whole_entry_listener.cc
@@ -0,0 +1,33 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/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( + Http2StringPiece error_message) {} + +// static +HpackWholeEntryNoOpListener* HpackWholeEntryNoOpListener::NoOpListener() { + static HpackWholeEntryNoOpListener* static_instance = + new HpackWholeEntryNoOpListener(); + return static_instance; +} + +} // namespace http2
diff --git a/http2/hpack/decoder/hpack_whole_entry_listener.h b/http2/hpack/decoder/hpack_whole_entry_listener.h new file mode 100644 index 0000000..2e559ce --- /dev/null +++ b/http2/hpack/decoder/hpack_whole_entry_listener.h
@@ -0,0 +1,80 @@ +// 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 "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { + +class HTTP2_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. + // error_message may be used in a GOAWAY frame as the Opaque Data. + virtual void OnHpackDecodeError(Http2StringPiece error_message) = 0; +}; + +// A no-op implementation of HpackWholeEntryDecoderListener, useful for ignoring +// callbacks once an error is detected. +class 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(Http2StringPiece error_message) 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/http2/hpack/hpack_static_table_entries.inc b/http2/hpack/hpack_static_table_entries.inc new file mode 100644 index 0000000..c6ae125 --- /dev/null +++ b/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/http2/hpack/hpack_string.cc b/http2/hpack/hpack_string.cc new file mode 100644 index 0000000..58bb821 --- /dev/null +++ b/http2/hpack/hpack_string.cc
@@ -0,0 +1,72 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/hpack_string.h" + +#include <utility> + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { + +HpackString::HpackString(const char* data) : str_(data) {} +HpackString::HpackString(Http2StringPiece str) : str_(Http2String(str)) {} +HpackString::HpackString(Http2String str) : str_(std::move(str)) {} +HpackString::HpackString(const HpackString& other) = default; +HpackString::~HpackString() = default; + +Http2StringPiece HpackString::ToStringPiece() const { + return str_; +} + +bool HpackString::operator==(const HpackString& other) const { + return str_ == other.str_; +} +bool HpackString::operator==(Http2StringPiece str) const { + return str == str_; +} + +bool operator==(Http2StringPiece a, const HpackString& b) { + return b == a; +} +bool operator!=(Http2StringPiece a, const HpackString& b) { + return !(b == a); +} +bool operator!=(const HpackString& a, const HpackString& b) { + return !(a == b); +} +bool operator!=(const HpackString& a, Http2StringPiece b) { + return !(a == b); +} +std::ostream& operator<<(std::ostream& out, const HpackString& v) { + return out << v.ToString(); +} + +HpackStringPair::HpackStringPair(const HpackString& name, + const HpackString& value) + : name(name), value(value) { + DVLOG(3) << DebugString() << " ctor"; +} + +HpackStringPair::HpackStringPair(Http2StringPiece name, Http2StringPiece value) + : name(name), value(value) { + DVLOG(3) << DebugString() << " ctor"; +} + +HpackStringPair::~HpackStringPair() { + DVLOG(3) << DebugString() << " dtor"; +} + +Http2String HpackStringPair::DebugString() const { + return Http2StrCat("HpackStringPair(name=", name.ToString(), + ", value=", value.ToString(), ")"); +} + +std::ostream& operator<<(std::ostream& os, const HpackStringPair& p) { + os << p.DebugString(); + return os; +} + +} // namespace http2
diff --git a/http2/hpack/hpack_string.h b/http2/hpack/hpack_string.h new file mode 100644 index 0000000..b1c499c --- /dev/null +++ b/http2/hpack/hpack_string.h
@@ -0,0 +1,75 @@ +// 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_HPACK_STRING_H_ +#define QUICHE_HTTP2_HPACK_HPACK_STRING_H_ + +// HpackString is currently a very simple container for a string, but allows us +// to relatively easily experiment with alternate string storage mechanisms for +// handling strings to be encoded with HPACK, or decoded from HPACK, such as +// a ref-counted string. + +#include <stddef.h> + +#include <iosfwd> + +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { + +class HTTP2_EXPORT_PRIVATE HpackString { + public: + explicit HpackString(const char* data); + explicit HpackString(Http2StringPiece str); + explicit HpackString(Http2String str); + HpackString(const HpackString& other); + + // Not sure yet whether this move ctor is required/sensible. + HpackString(HpackString&& other) = default; + + ~HpackString(); + + size_t size() const { return str_.size(); } + const Http2String& ToString() const { return str_; } + Http2StringPiece ToStringPiece() const; + + bool operator==(const HpackString& other) const; + + bool operator==(Http2StringPiece str) const; + + private: + Http2String str_; +}; + +HTTP2_EXPORT_PRIVATE bool operator==(Http2StringPiece a, const HpackString& b); +HTTP2_EXPORT_PRIVATE bool operator!=(Http2StringPiece a, const HpackString& b); +HTTP2_EXPORT_PRIVATE bool operator!=(const HpackString& a, + const HpackString& b); +HTTP2_EXPORT_PRIVATE bool operator!=(const HpackString& a, Http2StringPiece b); +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const HpackString& v); + +struct HTTP2_EXPORT_PRIVATE HpackStringPair { + HpackStringPair(const HpackString& name, const HpackString& value); + HpackStringPair(Http2StringPiece name, Http2StringPiece 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(); } + + Http2String DebugString() const; + + const HpackString name; + const HpackString value; +}; + +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os, + const HpackStringPair& p); + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_HPACK_STRING_H_
diff --git a/http2/hpack/hpack_string_test.cc b/http2/hpack/hpack_string_test.cc new file mode 100644 index 0000000..87f5975 --- /dev/null +++ b/http2/hpack/hpack_string_test.cc
@@ -0,0 +1,149 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/hpack_string.h" + +// Tests of HpackString. + +#include <utility> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" + +using ::testing::AssertionFailure; +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; + +namespace http2 { +namespace test { +namespace { + +const char kStr0[] = "s0: Some string to be copied into another string."; +const char kStr1[] = "S1 - some string to be copied into yet another string."; + +class HpackStringTest : public ::testing::Test { + protected: + AssertionResult VerifyNotEqual(HpackString* actual, + const Http2String& not_expected_str) { + const char* not_expected_ptr = not_expected_str.c_str(); + Http2StringPiece not_expected_sp(not_expected_str); + + VERIFY_NE(*actual, not_expected_ptr); + VERIFY_NE(*actual, not_expected_sp); + VERIFY_NE(*actual, not_expected_str); + VERIFY_NE(actual->ToStringPiece(), not_expected_sp); + + if (!(not_expected_ptr != *actual)) { + return AssertionFailure(); + } + if (!(not_expected_sp != *actual)) { + return AssertionFailure(); + } + if (!(not_expected_str != *actual)) { + return AssertionFailure(); + } + if (!(not_expected_sp != actual->ToStringPiece())) { + return AssertionFailure(); + } + + return AssertionSuccess(); + } + + AssertionResult VerifyEqual(HpackString* actual, + const Http2String& expected_str) { + VERIFY_EQ(actual->size(), expected_str.size()); + + const char* expected_ptr = expected_str.c_str(); + const Http2StringPiece expected_sp(expected_str); + + VERIFY_EQ(*actual, expected_ptr); + VERIFY_EQ(*actual, expected_sp); + VERIFY_EQ(*actual, expected_str); + VERIFY_EQ(actual->ToStringPiece(), expected_sp); + + if (!(expected_sp == *actual)) { + return AssertionFailure(); + } + if (!(expected_ptr == *actual)) { + return AssertionFailure(); + } + if (!(expected_str == *actual)) { + return AssertionFailure(); + } + if (!(expected_sp == actual->ToStringPiece())) { + return AssertionFailure(); + } + + return AssertionSuccess(); + } +}; + +TEST_F(HpackStringTest, CharArrayConstructor) { + HpackString hs0(kStr0); + EXPECT_TRUE(VerifyEqual(&hs0, kStr0)); + EXPECT_TRUE(VerifyNotEqual(&hs0, kStr1)); + + HpackString hs1(kStr1); + EXPECT_TRUE(VerifyEqual(&hs1, kStr1)); + EXPECT_TRUE(VerifyNotEqual(&hs1, kStr0)); +} + +TEST_F(HpackStringTest, StringPieceConstructor) { + Http2StringPiece sp0(kStr0); + HpackString hs0(sp0); + EXPECT_TRUE(VerifyEqual(&hs0, kStr0)); + EXPECT_TRUE(VerifyNotEqual(&hs0, kStr1)); + + Http2StringPiece sp1(kStr1); + HpackString hs1(sp1); + EXPECT_TRUE(VerifyEqual(&hs1, kStr1)); + EXPECT_TRUE(VerifyNotEqual(&hs1, kStr0)); +} + +TEST_F(HpackStringTest, MoveStringConstructor) { + Http2String str0(kStr0); + HpackString hs0(str0); + EXPECT_TRUE(VerifyEqual(&hs0, kStr0)); + EXPECT_TRUE(VerifyNotEqual(&hs0, kStr1)); + + Http2String str1(kStr1); + HpackString hs1(str1); + EXPECT_TRUE(VerifyEqual(&hs1, kStr1)); + EXPECT_TRUE(VerifyNotEqual(&hs1, kStr0)); +} + +TEST_F(HpackStringTest, CopyConstructor) { + Http2StringPiece sp0(kStr0); + HpackString hs0(sp0); + HpackString hs1(hs0); + EXPECT_EQ(hs0, hs1); + + EXPECT_TRUE(VerifyEqual(&hs0, kStr0)); + EXPECT_TRUE(VerifyEqual(&hs1, kStr0)); + + EXPECT_TRUE(VerifyNotEqual(&hs0, kStr1)); + EXPECT_TRUE(VerifyNotEqual(&hs1, kStr1)); +} + +TEST_F(HpackStringTest, MoveConstructor) { + Http2StringPiece sp0(kStr0); + HpackString hs0(sp0); + EXPECT_TRUE(VerifyEqual(&hs0, kStr0)); + EXPECT_TRUE(VerifyNotEqual(&hs0, "")); + + HpackString hs1(std::move(hs0)); + EXPECT_NE(hs0, hs1); + + EXPECT_TRUE(VerifyEqual(&hs1, kStr0)); + EXPECT_TRUE(VerifyEqual(&hs0, "")); + EXPECT_TRUE(VerifyNotEqual(&hs1, "")); + + LOG(INFO) << hs0; + LOG(INFO) << hs1; +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/hpack/http2_hpack_constants.cc b/http2/hpack/http2_hpack_constants.cc new file mode 100644 index 0000000..f258ab9 --- /dev/null +++ b/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 "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" + +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { + +Http2String 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 Http2StrCat("UnknownHpackEntryType(", static_cast<int>(v), ")"); +} + +std::ostream& operator<<(std::ostream& out, HpackEntryType v) { + return out << HpackEntryTypeToString(v); +} + +} // namespace http2
diff --git a/http2/hpack/http2_hpack_constants.h b/http2/hpack/http2_hpack_constants.h new file mode 100644 index 0000000..de0d685 --- /dev/null +++ b/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 "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.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. +HTTP2_EXPORT_PRIVATE Http2String HpackEntryTypeToString(HpackEntryType v); + +// Inserts the name of the enum member into |out|. +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + HpackEntryType v); + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_HTTP2_HPACK_CONSTANTS_H_
diff --git a/http2/hpack/http2_hpack_constants_test.cc b/http2/hpack/http2_hpack_constants_test.cc new file mode 100644 index 0000000..5b20b18 --- /dev/null +++ b/http2/hpack/http2_hpack_constants_test.cc
@@ -0,0 +1,72 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" + +#include "base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_mock_log.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) { + { + CREATE_HTTP2_MOCK_LOG(log); + log.StartCapturingLogs(); + EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "kIndexedHeader"); + LOG(INFO) << HpackEntryType::kIndexedHeader; + } + { + CREATE_HTTP2_MOCK_LOG(log); + log.StartCapturingLogs(); + EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "kDynamicTableSizeUpdate"); + LOG(INFO) << HpackEntryType::kDynamicTableSizeUpdate; + } + { + CREATE_HTTP2_MOCK_LOG(log); + log.StartCapturingLogs(); + EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "kIndexedLiteralHeader"); + LOG(INFO) << HpackEntryType::kIndexedLiteralHeader; + } + { + CREATE_HTTP2_MOCK_LOG(log); + log.StartCapturingLogs(); + EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "kUnindexedLiteralHeader"); + LOG(INFO) << HpackEntryType::kUnindexedLiteralHeader; + } + { + CREATE_HTTP2_MOCK_LOG(log); + log.StartCapturingLogs(); + EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "kNeverIndexedLiteralHeader"); + LOG(INFO) << HpackEntryType::kNeverIndexedLiteralHeader; + } + { + CREATE_HTTP2_MOCK_LOG(log); + log.StartCapturingLogs(); + EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "UnknownHpackEntryType(1234321)"); + LOG(INFO) << static_cast<HpackEntryType>(1234321); + } +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/hpack/huffman/hpack_huffman_decoder.cc b/http2/hpack/huffman/hpack_huffman_decoder.cc new file mode 100644 index 0000000..d1076ed --- /dev/null +++ b/http2/hpack/huffman/hpack_huffman_decoder.cc
@@ -0,0 +1,487 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h" + +#include <bitset> +#include <limits> + +#include "base/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. + +// TODO(jamessynge): Remove use of binary literals, that is a C++ 14 feature. + +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(Http2StringPiece 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) { + 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. + DCHECK_EQ(accumulator_ & ~expected, 0u) + << "\n expected: " << HuffmanAccumulatorBitSet(expected) << "\n " + << *this; + return accumulator_ == expected; + } + return false; +} + +Http2String HuffmanBitBuffer::DebugString() const { + std::stringstream ss; + ss << "{accumulator: " << HuffmanAccumulatorBitSet(accumulator_) + << "; count: " << count_ << "}"; + return ss.str(); +} + +HpackHuffmanDecoder::HpackHuffmanDecoder() = default; + +HpackHuffmanDecoder::~HpackHuffmanDecoder() = default; + +bool HpackHuffmanDecoder::Decode(Http2StringPiece input, Http2String* output) { + DVLOG(1) << "HpackHuffmanDecoder::Decode"; + + // Fill bit_buffer_ from input. + input.remove_prefix(bit_buffer_.AppendBytes(input)); + + while (true) { + 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); + 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; + DVLOG(3) << "code_prefix: " << HuffmanCodeBitSet(code_prefix); + + PrefixInfo prefix_info = PrefixToInfo(code_prefix); + DVLOG(3) << "prefix_info: " << prefix_info; + DCHECK_LE(kMinCodeBitCount, prefix_info.code_length); + 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. + 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) { + DCHECK_EQ(input.size(), 0u); + return true; + } + input.remove_prefix(byte_count); + } +} + +Http2String HpackHuffmanDecoder::DebugString() const { + return bit_buffer_.DebugString(); +} + +} // namespace http2
diff --git a/http2/hpack/huffman/hpack_huffman_decoder.h b/http2/hpack/huffman/hpack_huffman_decoder.h new file mode 100644 index 0000000..8e511d6 --- /dev/null +++ b/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 "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +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 HTTP2_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(Http2StringPiece 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; + + Http2String DebugString() const; + + private: + HuffmanAccumulator accumulator_; + HuffmanAccumulatorBitCount count_; +}; + +inline std::ostream& operator<<(std::ostream& out, const HuffmanBitBuffer& v) { + return out << v.DebugString(); +} + +class HTTP2_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(Http2StringPiece input, Http2String* 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(); + } + + Http2String 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/http2/hpack/huffman/hpack_huffman_decoder_test.cc b/http2/hpack/huffman/hpack_huffman_decoder_test.cc new file mode 100644 index 0000000..6c07ca5 --- /dev/null +++ b/http2/hpack/huffman/hpack_huffman_decoder_test.cc
@@ -0,0 +1,245 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h" + +// Tests of HpackHuffmanDecoder and HuffmanBitBuffer. + +#include <iostream> + +#include "base/macros.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_arraysize.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +using ::testing::AssertionResult; + +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) { + Http2String s; + s.push_back('\x11'); + s.push_back('\x22'); + s.push_back('\x33'); + Http2StringPiece 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) { + Http2String s; + s.push_back('\x11'); + s.push_back('\x22'); + s.push_back('\x33'); + Http2StringPiece 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) { + Http2String 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'); + Http2StringPiece 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(); + Http2StringPiece sp(b->cursor(), b->Remaining()); + if (decoder_.Decode(sp, &output_buffer_)) { + b->AdvanceCursor(b->Remaining()); + // Successfully decoded (or buffered) the bytes in Http2StringPiece. + 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_; + Http2String output_buffer_; + size_t input_bytes_seen_; + size_t input_bytes_expected_; +}; + +TEST_F(HpackHuffmanDecoderTest, SpecRequestExamples) { + HpackHuffmanDecoder decoder; + Http2String test_table[] = { + Http2HexDecode("f1e3c2e5f23a6ba0ab90f4ff"), + "www.example.com", + Http2HexDecode("a8eb10649cbf"), + "no-cache", + Http2HexDecode("25a849e95ba97d7f"), + "custom-key", + Http2HexDecode("25a849e95bb8e8b4bf"), + "custom-value", + }; + for (size_t i = 0; i != HTTP2_ARRAYSIZE(test_table); i += 2) { + const Http2String& huffman_encoded(test_table[i]); + const Http2String& plain_string(test_table[i + 1]); + Http2String 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 + Http2String test_table[] = { + Http2HexDecode("6402"), + "302", + Http2HexDecode("aec3771a4b"), + "private", + Http2HexDecode("d07abe941054d444a8200595040b8166" + "e082a62d1bff"), + "Mon, 21 Oct 2013 20:13:21 GMT", + Http2HexDecode("9d29ad171863c78f0b97c8e9ae82ae43" + "d3"), + "https://www.example.com", + Http2HexDecode("94e7821dd7f2e6c7b335dfdfcd5b3960" + "d5af27087f3672c1ab270fb5291f9587" + "316065c003ed4ee5b1063d5007"), + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", + }; + // clang-format on + for (size_t i = 0; i != HTTP2_ARRAYSIZE(test_table); i += 2) { + const Http2String& huffman_encoded(test_table[i]); + const Http2String& plain_string(test_table[i + 1]); + Http2String 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/http2/hpack/huffman/hpack_huffman_encoder.cc b/http2/hpack/huffman/hpack_huffman_encoder.cc new file mode 100644 index 0000000..9f561ee --- /dev/null +++ b/http2/hpack/huffman/hpack_huffman_encoder.cc
@@ -0,0 +1,110 @@ +// 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 "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/hpack/huffman/huffman_spec_tables.h" + +// TODO(jamessynge): Remove use of binary literals, that is a C++ 14 feature. + +namespace http2 { + +size_t ExactHuffmanSize(Http2StringPiece plain) { + size_t bits = 0; + for (const uint8_t c : plain) { + bits += HuffmanSpecTables::kCodeLengths[c]; + } + return (bits + 7) / 8; +} + +size_t BoundedHuffmanSize(Http2StringPiece plain) { + // TODO(jamessynge): Determine whether we should set the min size for Huffman + // encoding much higher (i.e. if less than N, then the savings isn't worth + // the cost of encoding and decoding). Of course, we need to decide on a + // value function, which might be throughput on a full load test, or a + // microbenchmark of the time to encode and then decode a HEADERS frame, + // possibly with the cost of crypto included (i.e. crypto is going to have + // a fairly constant per-byte cost, so reducing the number of bytes in-transit + // reduces the number that must be encrypted and later decrypted). + if (plain.size() < 3) { + // Huffman encoded string can't be smaller than the plain size for very + // short strings. + return plain.size(); + } + // TODO(jamessynge): Measure whether this can be done more efficiently with + // nested loops (e.g. make exact measurement of 8 bytes, then check if min + // remaining is too long). + // Compute the number of bits in an encoding that is shorter than the plain + // string (i.e. the number of bits in a string 1 byte shorter than plain), + // and use this as the limit of the size of the encoding. + const size_t limit_bits = (plain.size() - 1) * 8; + // The shortest code length in the Huffman table of the HPACK spec has 5 bits + // (e.g. for 0, 1, a and e). + const size_t min_code_length = 5; + // We can therefore say that all plain text bytes whose code length we've not + // yet looked up will take at least 5 bits. + size_t min_bits_remaining = plain.size() * min_code_length; + size_t bits = 0; + for (const uint8_t c : plain) { + bits += HuffmanSpecTables::kCodeLengths[c]; + min_bits_remaining -= min_code_length; + // If our minimum estimate of the total number of bits won't yield an + // encoding shorter the plain text, let's bail. + const size_t minimum_bits_total = bits + min_bits_remaining; + if (minimum_bits_total > limit_bits) { + bits += min_bits_remaining; + break; + } + } + return (bits + 7) / 8; +} + +void HuffmanEncode(Http2StringPiece plain, Http2String* huffman) { + DCHECK(huffman != nullptr); + huffman->clear(); // Note that this doesn't release memory. + 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); + } +} + +} // namespace http2
diff --git a/http2/hpack/huffman/hpack_huffman_encoder.h b/http2/hpack/huffman/hpack_huffman_encoder.h new file mode 100644 index 0000000..bf6b1b2 --- /dev/null +++ b/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 "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { + +// Returns the size of the Huffman encoding of |plain|, which may be greater +// than plain.size(). Mostly present for testing. +HTTP2_EXPORT_PRIVATE size_t ExactHuffmanSize(Http2StringPiece plain); + +// Returns the size of the Huffman encoding of |plain|, unless it is greater +// than or equal to plain.size(), in which case a value greater than or equal to +// plain.size() is returned. The advantage of this over ExactHuffmanSize is that +// it doesn't read as much of the input string in the event that the string is +// not compressible by HuffmanEncode (i.e. when the encoding is longer than the +// original string, it stops reading the input string as soon as it knows that). +HTTP2_EXPORT_PRIVATE size_t BoundedHuffmanSize(Http2StringPiece plain); + +// Encode the plain text string |plain| with the Huffman encoding defined in +// the HPACK RFC, 7541. |*huffman| does not have to be empty, it is cleared at +// the beginning of this function. This allows reusing the same string object +// across multiple invocations. +HTTP2_EXPORT_PRIVATE void HuffmanEncode(Http2StringPiece plain, + Http2String* huffman); + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_HUFFMAN_HPACK_HUFFMAN_ENCODER_H_
diff --git a/http2/hpack/huffman/hpack_huffman_encoder_test.cc b/http2/hpack/huffman/hpack_huffman_encoder_test.cc new file mode 100644 index 0000000..ccb6983 --- /dev/null +++ b/http2/hpack/huffman/hpack_huffman_encoder_test.cc
@@ -0,0 +1,97 @@ +// 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 "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.h" + +#include "base/macros.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_arraysize.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { +namespace { + +TEST(HuffmanEncoderTest, SpecRequestExamples) { + Http2String test_table[] = { + Http2HexDecode("f1e3c2e5f23a6ba0ab90f4ff"), + "www.example.com", + Http2HexDecode("a8eb10649cbf"), + "no-cache", + Http2HexDecode("25a849e95ba97d7f"), + "custom-key", + Http2HexDecode("25a849e95bb8e8b4bf"), + "custom-value", + }; + for (size_t i = 0; i != HTTP2_ARRAYSIZE(test_table); i += 2) { + const Http2String& huffman_encoded(test_table[i]); + const Http2String& plain_string(test_table[i + 1]); + EXPECT_EQ(ExactHuffmanSize(plain_string), huffman_encoded.size()); + EXPECT_EQ(BoundedHuffmanSize(plain_string), huffman_encoded.size()); + Http2String buffer; + buffer.reserve(); + HuffmanEncode(plain_string, &buffer); + EXPECT_EQ(buffer, huffman_encoded) << "Error encoding " << plain_string; + } +} + +TEST(HuffmanEncoderTest, SpecResponseExamples) { + // clang-format off + Http2String test_table[] = { + Http2HexDecode("6402"), + "302", + Http2HexDecode("aec3771a4b"), + "private", + Http2HexDecode("d07abe941054d444a8200595040b8166" + "e082a62d1bff"), + "Mon, 21 Oct 2013 20:13:21 GMT", + Http2HexDecode("9d29ad171863c78f0b97c8e9ae82ae43" + "d3"), + "https://www.example.com", + Http2HexDecode("94e7821dd7f2e6c7b335dfdfcd5b3960" + "d5af27087f3672c1ab270fb5291f9587" + "316065c003ed4ee5b1063d5007"), + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", + }; + // clang-format on + for (size_t i = 0; i != HTTP2_ARRAYSIZE(test_table); i += 2) { + const Http2String& huffman_encoded(test_table[i]); + const Http2String& plain_string(test_table[i + 1]); + EXPECT_EQ(ExactHuffmanSize(plain_string), huffman_encoded.size()); + EXPECT_EQ(BoundedHuffmanSize(plain_string), huffman_encoded.size()); + Http2String buffer; + buffer.reserve(huffman_encoded.size()); + const size_t capacity = buffer.capacity(); + HuffmanEncode(plain_string, &buffer); + EXPECT_EQ(buffer, huffman_encoded) << "Error encoding " << plain_string; + EXPECT_EQ(capacity, buffer.capacity()); + } +} + +TEST(HuffmanEncoderTest, EncodedSizeAgreesWithEncodeString) { + Http2String test_table[] = { + "", + "Mon, 21 Oct 2013 20:13:21 GMT", + "https://www.example.com", + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", + Http2String(1, '\0'), + Http2String("foo\0bar", 7), + Http2String(256, '\0'), + }; + // Modify last |test_table| entry to cover all codes. + for (size_t i = 0; i != 256; ++i) { + test_table[HTTP2_ARRAYSIZE(test_table) - 1][i] = static_cast<char>(i); + } + + for (size_t i = 0; i != HTTP2_ARRAYSIZE(test_table); ++i) { + const Http2String& plain_string = test_table[i]; + Http2String huffman_encoded; + HuffmanEncode(plain_string, &huffman_encoded); + EXPECT_EQ(huffman_encoded.size(), ExactHuffmanSize(plain_string)); + EXPECT_LE(BoundedHuffmanSize(plain_string), plain_string.size()); + EXPECT_LE(BoundedHuffmanSize(plain_string), ExactHuffmanSize(plain_string)); + } +} + +} // namespace +} // namespace http2
diff --git a/http2/hpack/huffman/hpack_huffman_transcoder_test.cc b/http2/hpack/huffman/hpack_huffman_transcoder_test.cc new file mode 100644 index 0000000..cfd73b5 --- /dev/null +++ b/http2/hpack/huffman/hpack_huffman_transcoder_test.cc
@@ -0,0 +1,184 @@ +// 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 "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h" +#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using ::testing::tuple; + +namespace http2 { +namespace test { +namespace { + +Http2String GenAsciiNonControlSet() { + Http2String 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(); + Http2StringPiece sp(b->cursor(), b->Remaining()); + if (decoder_.Decode(sp, &output_buffer_)) { + b->AdvanceCursor(b->Remaining()); + // Successfully decoded (or buffered) the bytes in Http2StringPiece. + 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( + Http2StringPiece plain, + Http2StringPiece expected_huffman) { + Http2String encoded; + HuffmanEncode(plain, &encoded); + if (expected_huffman.size() > 0 || 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(Http2StringPiece plain) { + return TranscodeAndValidateSeveralWays(plain, ""); + } + + Http2String RandomAsciiNonControlString(int length) { + return Random().RandStringWithAlphabet(length, ascii_non_control_set_); + } + + Http2String RandomBytes(int length) { return Random().RandString(length); } + + const Http2String ascii_non_control_set_; + HpackHuffmanDecoder decoder_; + Http2String 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 Http2String s = RandomAsciiNonControlString(length); + ASSERT_TRUE(TranscodeAndValidateSeveralWays(s)) + << "Unable to decode:\n\n" + << Http2HexDump(s) << "\n\noutput_buffer_:\n" + << Http2HexDump(output_buffer_); + } +} + +TEST_F(HpackHuffmanTranscoderTest, RoundTripRandomBytes) { + for (size_t length = 0; length != 20; length++) { + const Http2String s = RandomBytes(length); + ASSERT_TRUE(TranscodeAndValidateSeveralWays(s)) + << "Unable to decode:\n\n" + << Http2HexDump(s) << "\n\noutput_buffer_:\n" + << Http2HexDump(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_CASE_P(HpackHuffmanTranscoderAdjacentCharTest, + HpackHuffmanTranscoderAdjacentCharTest, + ::testing::Range(0, 256)); + +// Test c_ adjacent to every other character, both before and after. +TEST_P(HpackHuffmanTranscoderAdjacentCharTest, RoundTripAdjacentChar) { + Http2String 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<tuple<int, int>> { + protected: + HpackHuffmanTranscoderRepeatedCharTest() + : c_(static_cast<char>(::testing::get<0>(GetParam()))), + length_(::testing::get<1>(GetParam())) {} + Http2String MakeString() { return Http2String(length_, c_); } + + private: + const char c_; + const size_t length_; +}; + +INSTANTIATE_TEST_CASE_P( + HpackHuffmanTranscoderRepeatedCharTest, + HpackHuffmanTranscoderRepeatedCharTest, + ::testing::Combine(::testing::Range(0, 256), + ::testing::Values(1, 2, 3, 4, 8, 16, 32))); + +TEST_P(HpackHuffmanTranscoderRepeatedCharTest, RoundTripRepeatedChar) { + ASSERT_TRUE(TranscodeAndValidateSeveralWays(MakeString())); +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/hpack/huffman/huffman_spec_tables.cc b/http2/hpack/huffman/huffman_spec_tables.cc new file mode 100644 index 0000000..27707b9 --- /dev/null +++ b/http2/hpack/huffman/huffman_spec_tables.cc
@@ -0,0 +1,578 @@ +// 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 "net/third_party/quiche/src/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 +}; + +// TODO(jamessynge): Remove use of binary literals, that is a C++ 14 feature. + +// Uncomment these codes if needed for generating Huffman output, as opposed +// to decoding Huffman input. +/* +// 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/http2/hpack/huffman/huffman_spec_tables.h b/http2/hpack/huffman/huffman_spec_tables.h new file mode 100644 index 0000000..6cd8726 --- /dev/null +++ b/http2/hpack/huffman/huffman_spec_tables.h
@@ -0,0 +1,25 @@ +// 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]; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_HUFFMAN_HUFFMAN_SPEC_TABLES_H_
diff --git a/http2/hpack/tools/hpack_block_builder.cc b/http2/hpack/tools/hpack_block_builder.cc new file mode 100644 index 0000000..7a7d348 --- /dev/null +++ b/http2/hpack/tools/hpack_block_builder.cc
@@ -0,0 +1,80 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.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, 7); + + HpackVarintEncoder varint_encoder; + + unsigned char c = + varint_encoder.StartEncoding(high_bits, prefix_length, varint); + buffer_.push_back(c); + + if (!varint_encoder.IsEncodingInProgress()) { + return; + } + + // After the prefix, at most 63 bits can remain to be encoded. + // Each octet holds 7 bits, so at most 9 octets are necessary. + // TODO(bnc): Move this into a constant in HpackVarintEncoder. + varint_encoder.ResumeEncoding(/* max_encoded_bytes = */ 10, &buffer_); + DCHECK(!varint_encoder.IsEncodingInProgress()); +} + +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 << "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, + Http2StringPiece 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/http2/hpack/tools/hpack_block_builder.h b/http2/hpack/tools/hpack_block_builder.h new file mode 100644 index 0000000..560953c --- /dev/null +++ b/http2/hpack/tools/hpack_block_builder.h
@@ -0,0 +1,96 @@ +// 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 DCHECKs. And of course +// the support for very large varints will not be needed in production code. + +#include <stddef.h> + +#include <cstdint> + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { +namespace test { + +class HpackBlockBuilder { + public: + explicit HpackBlockBuilder(Http2StringPiece initial_contents) + : buffer_(initial_contents.data(), initial_contents.size()) {} + HpackBlockBuilder() {} + ~HpackBlockBuilder() {} + + size_t size() const { return buffer_.size(); } + const Http2String& 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, + Http2StringPiece 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, + Http2StringPiece name, + bool value_is_huffman_encoded, + Http2StringPiece 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, Http2StringPiece str); + + private: + Http2String buffer_; +}; + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_TOOLS_HPACK_BLOCK_BUILDER_H_
diff --git a/http2/hpack/tools/hpack_block_builder_test.cc b/http2/hpack/tools/hpack_block_builder_test.cc new file mode 100644 index 0000000..a7b8064 --- /dev/null +++ b/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 "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.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 Http2String expected = + Http2HexDecode("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, + Http2StringPiece(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 Http2String expected = + Http2HexDecode("828684418cf1e3c2e5f23a6ba0ab90f4ff"); + EXPECT_EQ(expected, b.buffer()); + } +} + +TEST(HpackBlockBuilderTest, DynamicTableSizeUpdate) { + { + HpackBlockBuilder b; + b.AppendDynamicTableSizeUpdate(0); + EXPECT_EQ(1u, b.size()); + + const char kData[] = {'\x20'}; + Http2StringPiece 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'}; + Http2StringPiece 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'}; + Http2StringPiece expected(kData, sizeof kData); + EXPECT_EQ(expected, b.buffer()); + } +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/hpack/tools/hpack_example.cc b/http2/hpack/tools/hpack_example.cc new file mode 100644 index 0000000..d20eb35 --- /dev/null +++ b/http2/hpack/tools/hpack_example.cc
@@ -0,0 +1,58 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_example.h" + +#include <ctype.h> + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { +namespace test { +namespace { + +void HpackExampleToStringOrDie(Http2StringPiece example, Http2String* output) { + while (!example.empty()) { + const char c0 = example[0]; + if (isxdigit(c0)) { + CHECK_GT(example.size(), 1u) << "Truncated hex byte?"; + const char c1 = example[1]; + CHECK(isxdigit(c1)) << "Found half a byte?"; + *output += Http2HexDecode(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 == Http2StringPiece::npos) { + // End of input. + break; + } + example.remove_prefix(pos + 1); + continue; + } + HTTP2_BUG << "Can't parse byte " << static_cast<int>(c0) + << Http2StrCat(" (0x", Http2Hex(c0), ")") + << "\nExample: " << example; + } + CHECK_LT(0u, output->size()) << "Example is empty."; +} + +} // namespace + +Http2String HpackExampleToStringOrDie(Http2StringPiece example) { + Http2String output; + HpackExampleToStringOrDie(example, &output); + return output; +} + +} // namespace test +} // namespace http2
diff --git a/http2/hpack/tools/hpack_example.h b/http2/hpack/tools/hpack_example.h new file mode 100644 index 0000000..ddb22a3 --- /dev/null +++ b/http2/hpack/tools/hpack_example.h
@@ -0,0 +1,31 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_TOOLS_HPACK_EXAMPLE_H_ +#define QUICHE_HTTP2_HPACK_TOOLS_HPACK_EXAMPLE_H_ + +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +// 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 { + +Http2String HpackExampleToStringOrDie(Http2StringPiece example); + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_TOOLS_HPACK_EXAMPLE_H_
diff --git a/http2/hpack/varint/hpack_varint_decoder.cc b/http2/hpack/varint/hpack_varint_decoder.cc new file mode 100644 index 0000000..1f9b6f6 --- /dev/null +++ b/http2/hpack/varint/hpack_varint_decoder.cc
@@ -0,0 +1,172 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h" + +#include "net/third_party/quiche/src/http2/platform/api/http2_flag_utils.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { + +DecodeStatus HpackVarintDecoder::Start(uint8_t prefix_value, + uint8_t prefix_length, + DecodeBuffer* db) { + DCHECK_LE(3u, prefix_length); + DCHECK_LE(prefix_length, 7u); + + // |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) { + DCHECK_LE(3u, prefix_length); + DCHECK_LE(prefix_length, 7u); + + value_ = (1 << prefix_length) - 1; + offset_ = 0; + return Resume(db); +} + +DecodeStatus HpackVarintDecoder::Resume(DecodeBuffer* db) { + if (decode_64_bits_) { + HTTP2_RELOADABLE_FLAG_COUNT(http2_varint_decode_64_bits); + // 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. + DCHECK_LE(offset_, 56); + 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. + 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; + } + + 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. + DLOG(WARNING) << "Variable length int encoding is too large or too long. " + << DebugString(); + MarkDone(); + return DecodeStatus::kDecodeError; + } + + // Old code path. TODO(bnc): remove. + DCHECK(!decode_64_bits_); + const uint8_t kMaxOffset = 28; + CheckNotDone(); + do { + if (db->Empty()) { + return DecodeStatus::kDecodeInProgress; + } + uint8_t byte = db->DecodeUInt8(); + if (offset_ == kMaxOffset && byte != 0) + break; + DCHECK(offset_ <= kMaxOffset - 7 || byte == 0); + // Shifting a 7 bit value to the left by at most 21 places can never + // overflow on uint32_t. Shifting 0 to the left cannot overflow either. + value_ += (byte & 0x7f) << offset_; + if ((byte & 0x80) == 0) { + MarkDone(); + return DecodeStatus::kDecodeDone; + } + offset_ += 7; + } while (offset_ <= kMaxOffset); + 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; +} + +Http2String HpackVarintDecoder::DebugString() const { + return Http2StrCat("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/http2/hpack/varint/hpack_varint_decoder.h b/http2/hpack/varint/hpack_varint_decoder.h new file mode 100644 index 0000000..ac8118d --- /dev/null +++ b/http2/hpack/varint/hpack_varint_decoder.h
@@ -0,0 +1,144 @@ +// 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 +// +// If GetHttp2ReloadableFlag(http2_varint_decode_64_bits) is true, then this +// decoder 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 +// +// If GetHttp2ReloadableFlag(http2_varint_decode_64_bits) is false, then this +// decoder supports decoding integers up to 2^28 + 2^prefix_length - 2. +// +// This decoder supports at most 10 extension bytes (bytes following the prefix, +// also called continuation bytes) if +// GetHttp2ReloadableFlag(http2_varint_decode_64_bits) is true, and at most 5 +// extension bytes if GetHttp2ReloadableFlag(http2_varint_decode_64_bits) is +// false. 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 "base/logging.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_flags.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.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 HTTP2_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 7 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. + + Http2String 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 + DCHECK_NE(kHpackVarintDecoderOffsetDone, offset_); +#endif + } + void CheckDone() const { +#ifndef NDEBUG + DCHECK_EQ(kHpackVarintDecoderOffsetDone, offset_); +#endif + } + + // If true, decode integers up to 2^64 - 1, and accept at most 10 extension + // bytes (some of which might be padding). + // If false, decode integers up to 2^28 + 2^prefix_length - 2, and accept at + // most 5 extension bytes (some of which might be padding). + bool decode_64_bits_ = GetHttp2ReloadableFlag(http2_varint_decode_64_bits); + + // 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/http2/hpack/varint/hpack_varint_decoder_test.cc b/http2/hpack/varint/hpack_varint_decoder_test.cc new file mode 100644 index 0000000..761087b --- /dev/null +++ b/http2/hpack/varint/hpack_varint_decoder_test.cc
@@ -0,0 +1,464 @@ +// 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 "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h" + +// Test HpackVarintDecoder against hardcoded data. + +#include <stddef.h> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_arraysize.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +using ::testing::AssertionFailure; +using ::testing::AssertionSuccess; + +namespace http2 { +namespace test { +namespace { + +// Save previous value of flag and restore on destruction. +class FlagSaver { + public: + FlagSaver() = delete; + explicit FlagSaver(bool decode_64_bits) + : saved_value_(GetHttp2ReloadableFlag(http2_varint_decode_64_bits)) { + SetHttp2ReloadableFlag(http2_varint_decode_64_bits, decode_64_bits); + } + ~FlagSaver() { + SetHttp2ReloadableFlag(http2_varint_decode_64_bits, saved_value_); + } + + private: + const bool saved_value_; +}; + +class HpackVarintDecoderTest + : public RandomDecoderTest, + public ::testing::WithParamInterface< + ::testing::tuple<bool, uint8_t, const char*>> { + protected: + HpackVarintDecoderTest() + : decode_64_bits_(::testing::get<0>(GetParam())), + high_bits_(::testing::get<1>(GetParam())), + suffix_(Http2HexDecode(::testing::get<2>(GetParam()))), + flag_saver_(decode_64_bits_), + prefix_length_(0) {} + + void DecodeExpectSuccess(Http2StringPiece 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(Http2StringPiece 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)); + } + + bool decode_64_bits() const { return decode_64_bits_; } + + private: + AssertionResult Decode(Http2StringPiece data, + uint32_t prefix_length, + const Validator validator) { + prefix_length_ = prefix_length; + + // Copy |data| so that it can be modified. + Http2String 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 { + 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); + } + + // Test new or old behavior. + const bool decode_64_bits_; + // Bits of the first byte not part of the prefix. + const uint8_t high_bits_; + // Extra bytes appended to the input. + const Http2String suffix_; + + // |flag_saver_| must preceed |decoder_| so that the flag is already set when + // |decoder_| is constructed. + FlagSaver flag_saver_; + HpackVarintDecoder decoder_; + uint8_t prefix_length_; +}; + +INSTANTIATE_TEST_CASE_P( + HpackVarintDecoderTest, + HpackVarintDecoderTest, + ::testing::Combine( + // Test both the new version (supporting 64 bit integers) and the old + // one (only supporting up to 2^28 + 2^prefix_length - 2. + ::testing::Bool(), + // 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"))); + +// Test data used when decode_64_bits() == true. +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}, + // Small values that fit in the prefix. + {"06", 3, 6}, + {"0d", 4, 13}, + {"10", 5, 16}, + {"29", 6, 41}, + {"56", 7, 86}, + // 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}, + // 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}, + // Values requiring one extension byte. + {"0760", 3, 103}, + {"0f2a", 4, 57}, + {"1f7f", 5, 158}, + {"3f02", 6, 65}, + {"7f49", 7, 200}, + // 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}, + // 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}, + // 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}, + // Values requiring two extension bytes. + {"07b260", 3, 12345}, + {"0f8a2a", 4, 5401}, + {"1fa87f", 5, 16327}, + {"3fd002", 6, 399}, + {"7fff49", 7, 9598}, + // 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}, + // 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}, + // Values requiring three extension bytes. + {"078ab260", 3, 1579281}, + {"0fc18a2a", 4, 689488}, + {"1fada87f", 5, 2085964}, + {"3fa0d002", 6, 43103}, + {"7ffeff49", 7, 1212541}, + // 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}, + // Values requiring four extension bytes. + {"079f8ab260", 3, 202147110}, + {"0fa2c18a2a", 4, 88252593}, + {"1fd0ada87f", 5, 266999535}, + {"3ff9a0d002", 6, 5509304}, + {"7f9efeff49", 7, 155189149}, + // 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}, + // Values requiring six extension bytes. + {"0783aa9f8ab260", 3, 3311978140938}, + {"0ff0b0a2c18a2a", 4, 1445930244223}, + {"1fda84d0ada87f", 5, 4374519874169}, + {"3fb5fbf9a0d002", 6, 90263420404}, + {"7fcff19efeff49", 7, 2542616951118}, + // Values requiring eight extension bytes. + {"07f19883aa9f8ab260", 3, 54263449861016696}, + {"0f84fdf0b0a2c18a2a", 4, 23690121121119891}, + {"1fa0dfda84d0ada87f", 5, 71672133617889215}, + {"3f9ff0b5fbf9a0d002", 6, 1478875878881374}, + {"7ffbc1cff19efeff49", 7, 41658236125045114}, + // Values requiring ten extension bytes. + {"0794f1f19883aa9f8ab201", 3, 12832019021693745307u}, + {"0fa08f84fdf0b0a2c18a01", 4, 9980690937382242223u}, + {"1fbfdda0dfda84d0ada801", 5, 12131360551794650846u}, + {"3f9dc79ff0b5fbf9a0d001", 6, 15006530362736632796u}, + {"7f8790fbc1cff19efeff01", 7, 18445754019193211014u}, + // Maximum value: 2^64-1. + {"07f8ffffffffffffffff01", 3, 18446744073709551615u}, + {"0ff0ffffffffffffffff01", 4, 18446744073709551615u}, + {"1fe0ffffffffffffffff01", 5, 18446744073709551615u}, + {"3fc0ffffffffffffffff01", 6, 18446744073709551615u}, + {"7f80ffffffffffffffff01", 7, 18446744073709551615u}, + // Examples from RFC7541 C.1. + {"0a", 5, 10}, + {"1f9a0a", 5, 1337}, +}; + +// Test data used when decode_64_bits() == false. +struct { + const char* data; + uint32_t prefix_length; + uint64_t expected_value; +} kSuccessTestDataOld[] = { + // Zero value with different prefix lengths. + {"00", 3, 0}, + {"00", 4, 0}, + {"00", 5, 0}, + {"00", 6, 0}, + {"00", 7, 0}, + // Small values that fit in the prefix. + {"06", 3, 6}, + {"0d", 4, 13}, + {"10", 5, 16}, + {"29", 6, 41}, + {"56", 7, 86}, + // 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}, + // 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}, + // Values requiring one extension byte. + {"0760", 3, 103}, + {"0f2a", 4, 57}, + {"1f7f", 5, 158}, + {"3f02", 6, 65}, + {"7f49", 7, 200}, + // 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}, + // 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}, + // Values requiring one extension byte, plus the maximum amount of padding. + {"07e080808000", 3, 103}, + {"0faa80808000", 4, 57}, + {"1fff80808000", 5, 158}, + {"3f8280808000", 6, 65}, + {"7fc980808000", 7, 200}, + // Values requiring two extension bytes. + {"07b260", 3, 12345}, + {"0f8a2a", 4, 5401}, + {"1fa87f", 5, 16327}, + {"3fd002", 6, 399}, + {"7fff49", 7, 9598}, + // 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}, + // Values requiring two extension bytes, plus the maximum amount of padding. + {"07b2e0808000", 3, 12345}, + {"0f8aaa808000", 4, 5401}, + {"1fa8ff808000", 5, 16327}, + {"3fd082808000", 6, 399}, + {"7fffc9808000", 7, 9598}, + // Values requiring three extension bytes. + {"078ab260", 3, 1579281}, + {"0fc18a2a", 4, 689488}, + {"1fada87f", 5, 2085964}, + {"3fa0d002", 6, 43103}, + {"7ffeff49", 7, 1212541}, + // 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}, + // Values requiring four extension bytes. + {"079f8ab260", 3, 202147110}, + {"0fa2c18a2a", 4, 88252593}, + {"1fd0ada87f", 5, 266999535}, + {"3ff9a0d002", 6, 5509304}, + {"7f9efeff49", 7, 155189149}, + // 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}, + // Examples from RFC7541 C.1. + {"0a", 5, 10}, + {"1f9a0a", 5, 1337}, +}; + +TEST_P(HpackVarintDecoderTest, Success) { + if (decode_64_bits()) { + for (size_t i = 0; i < HTTP2_ARRAYSIZE(kSuccessTestData); ++i) { + DecodeExpectSuccess(Http2HexDecode(kSuccessTestData[i].data), + kSuccessTestData[i].prefix_length, + kSuccessTestData[i].expected_value); + } + } else { + for (size_t i = 0; i < HTTP2_ARRAYSIZE(kSuccessTestDataOld); ++i) { + DecodeExpectSuccess(Http2HexDecode(kSuccessTestDataOld[i].data), + kSuccessTestDataOld[i].prefix_length, + kSuccessTestDataOld[i].expected_value); + } + } +} + +// Test data used when decode_64_bits() == true. +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}, + // Too many extension bytes, all 1s. + {"07ffffffffffffffffffff", 3}, + {"0fffffffffffffffffffff", 4}, + {"1fffffffffffffffffffff", 5}, + {"3fffffffffffffffffffff", 6}, + {"7fffffffffffffffffffff", 7}, + // Value of 2^64, one higher than maximum of 2^64-1. + {"07f9ffffffffffffffff01", 3}, + {"0ff1ffffffffffffffff01", 4}, + {"1fe1ffffffffffffffff01", 5}, + {"3fc1ffffffffffffffff01", 6}, + {"7f81ffffffffffffffff01", 7}, + // Maximum value: 2^64-1, with one byte of padding. + {"07f8ffffffffffffffff8100", 3}, + {"0ff0ffffffffffffffff8100", 4}, + {"1fe0ffffffffffffffff8100", 5}, + {"3fc0ffffffffffffffff8100", 6}, + {"7f80ffffffffffffffff8100", 7}, +}; + +// Test data used when decode_64_bits() == false. +// In this mode, HpackVarintDecoder allows at most five extension bytes, +// and fifth extension byte must be zero. +struct { + const char* data; + uint32_t prefix_length; +} kErrorTestDataOld[] = { + // Maximum number of extension bytes but last byte is non-zero. + {"078080808001", 3}, + {"0f8080808001", 4}, + {"1f8080808001", 5}, + {"3f8080808001", 6}, + {"7f8080808001", 7}, + // Too many extension bytes, all 0s (except for extension bit in each byte). + {"078080808080", 3}, + {"0f8080808080", 4}, + {"1f8080808080", 5}, + {"3f8080808080", 6}, + {"7f8080808080", 7}, + // Too many extension bytes, all 1s. + {"07ffffffffff", 3}, + {"0fffffffffff", 4}, + {"1fffffffffff", 5}, + {"3fffffffffff", 6}, + {"7fffffffffff", 7}, +}; + +TEST_P(HpackVarintDecoderTest, Error) { + if (decode_64_bits()) { + for (size_t i = 0; i < HTTP2_ARRAYSIZE(kErrorTestData); ++i) { + DecodeExpectError(Http2HexDecode(kErrorTestData[i].data), + kErrorTestData[i].prefix_length); + } + } else { + for (size_t i = 0; i < HTTP2_ARRAYSIZE(kErrorTestDataOld); ++i) { + DecodeExpectError(Http2HexDecode(kErrorTestDataOld[i].data), + kErrorTestDataOld[i].prefix_length); + } + } +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/hpack/varint/hpack_varint_encoder.cc b/http2/hpack/varint/hpack_varint_encoder.cc new file mode 100644 index 0000000..5872a0e --- /dev/null +++ b/http2/hpack/varint/hpack_varint_encoder.cc
@@ -0,0 +1,65 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.h" + +#include "base/logging.h" + +namespace http2 { + +HpackVarintEncoder::HpackVarintEncoder() + : varint_(0), encoding_in_progress_(false) {} + +unsigned char HpackVarintEncoder::StartEncoding(uint8_t high_bits, + uint8_t prefix_length, + uint64_t varint) { + DCHECK(!encoding_in_progress_); + DCHECK_EQ(0u, varint_); + DCHECK_LE(1u, prefix_length); + DCHECK_LE(prefix_length, 7u); + + // 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; + DCHECK_EQ(0, high_bits & prefix_mask); + + if (varint < prefix_mask) { + // The integer fits into the prefix in its entirety. + return high_bits | static_cast<unsigned char>(varint); + } + + // We need extension bytes. + varint_ = varint - prefix_mask; + encoding_in_progress_ = true; + return high_bits | prefix_mask; +} + +size_t HpackVarintEncoder::ResumeEncoding(size_t max_encoded_bytes, + Http2String* output) { + DCHECK(encoding_in_progress_); + DCHECK_NE(0u, max_encoded_bytes); + + size_t encoded_bytes = 0; + while (encoded_bytes < max_encoded_bytes) { + ++encoded_bytes; + if (varint_ < 128) { + // Encode final seven bits, with continuation bit set to zero. + output->push_back(varint_); + varint_ = 0; + encoding_in_progress_ = false; + break; + } + // Encode the next seven bits, with continuation bit set to one. + output->push_back(0b10000000 | (varint_ % 128)); + varint_ >>= 7; + } + return encoded_bytes; +} + +bool HpackVarintEncoder::IsEncodingInProgress() const { + return encoding_in_progress_; +} + +} // namespace http2
diff --git a/http2/hpack/varint/hpack_varint_encoder.h b/http2/hpack/varint/hpack_varint_encoder.h new file mode 100644 index 0000000..68f7474 --- /dev/null +++ b/http2/hpack/varint/hpack_varint_encoder.h
@@ -0,0 +1,51 @@ +// 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 <cstdint> + +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" + +namespace http2 { + +// HPACK integer encoder class implementing variable length integer +// representation defined in RFC7541, Section 5.1: +// https://httpwg.org/specs/rfc7541.html#integer.representation +class HTTP2_EXPORT_PRIVATE HpackVarintEncoder { + public: + HpackVarintEncoder(); + + // Start encoding an integer. Return the first encoded byte (composed of + // optional high bits and 1 to 8 bit long prefix). It is possible that this + // completes the encoding. Must not be called when previously started + // encoding is still in progress. + unsigned char StartEncoding(uint8_t high_bits, + uint8_t prefix_length, + uint64_t varint); + + // Continue encoding the integer |varint| passed in to StartEncoding(). + // Append the next at most |max_encoded_bytes| encoded octets to |output|. + // Returns the number of encoded octets. Must not be called unless a + // previously started encoding is still in progress. + size_t ResumeEncoding(size_t max_encoded_bytes, Http2String* output); + + // Returns true if encoding an integer has started and is not completed yet. + bool IsEncodingInProgress() const; + + private: + // The original integer shifted to the right by the number of bits already + // encoded. The lower bits shifted away have already been encoded, and + // |varint_| has the higher bits that remain to be encoded. + uint64_t varint_; + + // True when encoding an integer has started and is not completed yet. + bool encoding_in_progress_; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_VARINT_HPACK_VARINT_ENCODER_H_
diff --git a/http2/hpack/varint/hpack_varint_encoder_test.cc b/http2/hpack/varint/hpack_varint_encoder_test.cc new file mode 100644 index 0000000..d230af3 --- /dev/null +++ b/http2/hpack/varint/hpack_varint_encoder_test.cc
@@ -0,0 +1,178 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.h" + +#include "net/third_party/quiche/src/http2/platform/api/http2_arraysize.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace http2 { +namespace test { +namespace { + +// Freshly constructed encoder is not in the process of encoding. +TEST(HpackVarintEncoderTest, Done) { + HpackVarintEncoder varint_encoder; + EXPECT_FALSE(varint_encoder.IsEncodingInProgress()); +} + +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) { + HpackVarintEncoder varint_encoder; + + for (size_t i = 0; i < HTTP2_ARRAYSIZE(kShortTestData); ++i) { + EXPECT_EQ(kShortTestData[i].expected_encoding, + varint_encoder.StartEncoding(kShortTestData[i].high_bits, + kShortTestData[i].prefix_length, + kShortTestData[i].value)); + EXPECT_FALSE(varint_encoder.IsEncodingInProgress()); + } +} + +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) { + HpackVarintEncoder varint_encoder; + + // Test encoding byte by byte, also test encoding in + // a single ResumeEncoding() call. + for (bool byte_by_byte : {true, false}) { + for (size_t i = 0; i < HTTP2_ARRAYSIZE(kLongTestData); ++i) { + Http2String expected_encoding = + Http2HexDecode(kLongTestData[i].expected_encoding); + ASSERT_FALSE(expected_encoding.empty()); + + EXPECT_EQ(static_cast<unsigned char>(expected_encoding[0]), + varint_encoder.StartEncoding(kLongTestData[i].high_bits, + kLongTestData[i].prefix_length, + kLongTestData[i].value)); + EXPECT_TRUE(varint_encoder.IsEncodingInProgress()); + + Http2String output; + if (byte_by_byte) { + while (varint_encoder.IsEncodingInProgress()) { + EXPECT_EQ(1u, varint_encoder.ResumeEncoding(1, &output)); + } + } else { + // TODO(bnc): Factor out maximum number of extension bytes into a + // constant in HpackVarintEncoder. + EXPECT_EQ(expected_encoding.size() - 1, + varint_encoder.ResumeEncoding(10, &output)); + EXPECT_FALSE(varint_encoder.IsEncodingInProgress()); + } + EXPECT_EQ(expected_encoding.size() - 1, output.size()); + EXPECT_EQ(expected_encoding.substr(1), 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}}; + +// 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) { + HpackVarintEncoder varint_encoder; + + for (size_t i = 0; i < HTTP2_ARRAYSIZE(kLastByteIsZeroTestData); ++i) { + EXPECT_EQ( + kLastByteIsZeroTestData[i].expected_encoding_first_byte, + varint_encoder.StartEncoding(kLastByteIsZeroTestData[i].high_bits, + kLastByteIsZeroTestData[i].prefix_length, + kLastByteIsZeroTestData[i].value)); + EXPECT_TRUE(varint_encoder.IsEncodingInProgress()); + + Http2String output; + EXPECT_EQ(1u, varint_encoder.ResumeEncoding(1, &output)); + ASSERT_EQ(1u, output.size()); + EXPECT_EQ(0b00000000, output[0]); + EXPECT_FALSE(varint_encoder.IsEncodingInProgress()); + } +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/hpack/varint/hpack_varint_round_trip_test.cc b/http2/hpack/varint/hpack_varint_round_trip_test.cc new file mode 100644 index 0000000..e67b8df --- /dev/null +++ b/http2/hpack/varint/hpack_varint_round_trip_test.cc
@@ -0,0 +1,512 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/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 "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +using ::testing::AssertionFailure; +using ::testing::AssertionSuccess; +using ::testing::Bool; +using ::testing::WithParamInterface; + +namespace http2 { +namespace test { +namespace { + +// Save previous value of flag and restore on destruction. +class FlagSaver { + public: + FlagSaver() = delete; + explicit FlagSaver(bool decode_64_bits) + : saved_value_(GetHttp2ReloadableFlag(http2_varint_decode_64_bits)) { + SetHttp2ReloadableFlag(http2_varint_decode_64_bits, decode_64_bits); + } + ~FlagSaver() { + SetHttp2ReloadableFlag(http2_varint_decode_64_bits, saved_value_); + } + + private: + const bool saved_value_; +}; + +// 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, + public WithParamInterface<bool> { + protected: + HpackVarintRoundTripTest() + : decode_64_bits_(GetParam()), + flag_saver_(decode_64_bits_), + prefix_length_(0) {} + + DecodeStatus StartDecoding(DecodeBuffer* b) override { + 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) { + DCHECK_LE(3, prefix_length); + DCHECK_LE(prefix_length, 7); + 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(buffer_[0], 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) { + 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_. + + Http2String msg = Http2StrCat("value=", value, " (0x", Http2Hex(value), + "), prefix_length=", prefix_length, + ", expected_bytes=", expected_bytes, "\n", + Http2HexDump(buffer_)); + + if (value == minimum) { + LOG(INFO) << "Checking minimum; " << msg; + } else if (value == maximum) { + 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; + + LOG(INFO) << "############################################################"; + LOG(INFO) << "prefix_length=" << static_cast<int>(prefix_length); + LOG(INFO) << "prefix_mask=" << std::hex << static_cast<int>(prefix_mask); + LOG(INFO) << "start=" << start << " (" << std::hex << start << ")"; + LOG(INFO) << "range=" << range << " (" << std::hex << range << ")"; + LOG(INFO) << "beyond=" << beyond << " (" << std::hex << beyond << ")"; + 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()) << Http2HexDump(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); + } + + // |flag_saver_| must preceed |decoder_| so that the flag is already set when + // |decoder_| is constructed. + const bool decode_64_bits_; + FlagSaver flag_saver_; + HpackVarintDecoder decoder_; + Http2String buffer_; + uint8_t prefix_length_; +}; + +INSTANTIATE_TEST_CASE_P(HpackVarintRoundTripTest, + HpackVarintRoundTripTest, + Bool()); + +// To help me and future debuggers of varint encodings, this LOGs out the +// transition points where a new extension byte is added. +TEST_P(HpackVarintRoundTripTest, Encode) { + for (int prefix_length = 3; prefix_length <= 7; ++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); + + LOG(INFO) << "############################################################"; + 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. + }; + if (decode_64_bits_) { + for (auto value : { + 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. + }) { + values.push_back(value); + } + } + + for (uint64_t value : values) { + EncodeNoRandom(value, prefix_length); + Http2String dump = Http2HexDump(buffer_); + LOG(INFO) << Http2StringPrintf("%10llu %0#18x ", value, value) + << Http2HexDump(buffer_).substr(7); + } + } +} + +TEST_P(HpackVarintRoundTripTest, FromSpec1337) { + DecodeBuffer b(Http2StringPiece("\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_P(HpackVarintRoundTripTest, ValidatePrefixOnly) { + for (int prefix_length = 3; prefix_length <= 7; ++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_P(HpackVarintRoundTripTest, ValidateOneExtensionByte) { + for (int prefix_length = 3; prefix_length <= 7; ++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_P(HpackVarintRoundTripTest, ValidateTwoExtensionBytes) { + for (int prefix_length = 3; prefix_length <= 7; ++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_P(HpackVarintRoundTripTest, ValidateThreeExtensionBytes) { + for (int prefix_length = 3; prefix_length <= 7; ++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_P(HpackVarintRoundTripTest, ValidateFourExtensionBytes) { + for (int prefix_length = 3; prefix_length <= 7; ++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_P(HpackVarintRoundTripTest, ValidateFiveExtensionBytes) { + if (!decode_64_bits_) { + return; + } + + for (int prefix_length = 3; prefix_length <= 7; ++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_P(HpackVarintRoundTripTest, ValidateSixExtensionBytes) { + if (!decode_64_bits_) { + return; + } + + for (int prefix_length = 3; prefix_length <= 7; ++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_P(HpackVarintRoundTripTest, ValidateSevenExtensionBytes) { + if (!decode_64_bits_) { + return; + } + + for (int prefix_length = 3; prefix_length <= 7; ++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_P(HpackVarintRoundTripTest, ValidateEightExtensionBytes) { + if (!decode_64_bits_) { + return; + } + + for (int prefix_length = 3; prefix_length <= 7; ++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_P(HpackVarintRoundTripTest, ValidateNineExtensionBytes) { + if (!decode_64_bits_) { + return; + } + + for (int prefix_length = 3; prefix_length <= 7; ++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_P(HpackVarintRoundTripTest, ValidateTenExtensionBytes) { + if (!decode_64_bits_) { + return; + } + + for (int prefix_length = 3; prefix_length <= 7; ++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); + } +} + +// Test the value one larger than the largest that can be decoded. +TEST_P(HpackVarintRoundTripTest, ValueTooLarge) { + // New implementation can decode any integer that HpackVarintEncoder can + // encode. + if (decode_64_bits_) { + return; + } + + for (prefix_length_ = 3; prefix_length_ <= 7; ++prefix_length_) { + const uint64_t too_large = (1 << 28) + (1 << prefix_length_) - 1; + const uint32_t expected_offset = 6; + HpackBlockBuilder bb; + bb.AppendHighBitsAndVarint(0, prefix_length_, too_large); + buffer_ = bb.buffer(); + + // 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. + bool validated = false; + Validator validator = [&validated, expected_offset]( + const DecodeBuffer& db, + DecodeStatus status) -> AssertionResult { + validated = true; + VERIFY_EQ(DecodeStatus::kDecodeError, status); + VERIFY_EQ(expected_offset, db.Offset()); + return AssertionSuccess(); + }; + + // 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_offset, b.Offset()); + EXPECT_TRUE(validated); + } +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/http2_constants.cc b/http2/http2_constants.cc new file mode 100644 index 0000000..d4d59a5 --- /dev/null +++ b/http2/http2_constants.cc
@@ -0,0 +1,150 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/http2_constants.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { + +Http2String Http2FrameTypeToString(Http2FrameType v) { + switch (v) { + case Http2FrameType::DATA: + return "DATA"; + case Http2FrameType::HEADERS: + return "HEADERS"; + case Http2FrameType::PRIORITY: + return "PRIORITY"; + case Http2FrameType::RST_STREAM: + return "RST_STREAM"; + case Http2FrameType::SETTINGS: + return "SETTINGS"; + case Http2FrameType::PUSH_PROMISE: + return "PUSH_PROMISE"; + case Http2FrameType::PING: + return "PING"; + case Http2FrameType::GOAWAY: + return "GOAWAY"; + case Http2FrameType::WINDOW_UPDATE: + return "WINDOW_UPDATE"; + case Http2FrameType::CONTINUATION: + return "CONTINUATION"; + case Http2FrameType::ALTSVC: + return "ALTSVC"; + } + return Http2StrCat("UnknownFrameType(", static_cast<int>(v), ")"); +} + +Http2String Http2FrameTypeToString(uint8_t v) { + return Http2FrameTypeToString(static_cast<Http2FrameType>(v)); +} + +Http2String Http2FrameFlagsToString(Http2FrameType type, uint8_t flags) { + Http2String s; + // Closure to append flag name |v| to the Http2String |s|, + // and to clear |bit| from |flags|. + auto append_and_clear = [&s, &flags](Http2StringPiece v, uint8_t bit) { + if (!s.empty()) { + s.push_back('|'); + } + Http2StrAppend(&s, v); + flags ^= bit; + }; + if (flags & 0x01) { + if (type == Http2FrameType::DATA || type == Http2FrameType::HEADERS) { + append_and_clear("END_STREAM", Http2FrameFlag::END_STREAM); + } else if (type == Http2FrameType::SETTINGS || + type == Http2FrameType::PING) { + append_and_clear("ACK", Http2FrameFlag::ACK); + } + } + if (flags & 0x04) { + if (type == Http2FrameType::HEADERS || + type == Http2FrameType::PUSH_PROMISE || + type == Http2FrameType::CONTINUATION) { + append_and_clear("END_HEADERS", Http2FrameFlag::END_HEADERS); + } + } + if (flags & 0x08) { + if (type == Http2FrameType::DATA || type == Http2FrameType::HEADERS || + type == Http2FrameType::PUSH_PROMISE) { + append_and_clear("PADDED", Http2FrameFlag::PADDED); + } + } + if (flags & 0x20) { + if (type == Http2FrameType::HEADERS) { + append_and_clear("PRIORITY", Http2FrameFlag::PRIORITY); + } + } + if (flags != 0) { + append_and_clear(Http2StringPrintf("0x%02x", flags), flags); + } + DCHECK_EQ(0, flags); + return s; +} +Http2String Http2FrameFlagsToString(uint8_t type, uint8_t flags) { + return Http2FrameFlagsToString(static_cast<Http2FrameType>(type), flags); +} + +Http2String Http2ErrorCodeToString(uint32_t v) { + switch (v) { + case 0x0: + return "NO_ERROR"; + case 0x1: + return "PROTOCOL_ERROR"; + case 0x2: + return "INTERNAL_ERROR"; + case 0x3: + return "FLOW_CONTROL_ERROR"; + case 0x4: + return "SETTINGS_TIMEOUT"; + case 0x5: + return "STREAM_CLOSED"; + case 0x6: + return "FRAME_SIZE_ERROR"; + case 0x7: + return "REFUSED_STREAM"; + case 0x8: + return "CANCEL"; + case 0x9: + return "COMPRESSION_ERROR"; + case 0xa: + return "CONNECT_ERROR"; + case 0xb: + return "ENHANCE_YOUR_CALM"; + case 0xc: + return "INADEQUATE_SECURITY"; + case 0xd: + return "HTTP_1_1_REQUIRED"; + } + return Http2StrCat("UnknownErrorCode(0x", Http2Hex(v), ")"); +} +Http2String Http2ErrorCodeToString(Http2ErrorCode v) { + return Http2ErrorCodeToString(static_cast<uint32_t>(v)); +} + +Http2String Http2SettingsParameterToString(uint32_t v) { + switch (v) { + case 0x1: + return "HEADER_TABLE_SIZE"; + case 0x2: + return "ENABLE_PUSH"; + case 0x3: + return "MAX_CONCURRENT_STREAMS"; + case 0x4: + return "INITIAL_WINDOW_SIZE"; + case 0x5: + return "MAX_FRAME_SIZE"; + case 0x6: + return "MAX_HEADER_LIST_SIZE"; + } + return Http2StrCat("UnknownSettingsParameter(0x", Http2Hex(v), ")"); +} +Http2String Http2SettingsParameterToString(Http2SettingsParameter v) { + return Http2SettingsParameterToString(static_cast<uint32_t>(v)); +} + +} // namespace http2
diff --git a/http2/http2_constants.h b/http2/http2_constants.h new file mode 100644 index 0000000..9392006 --- /dev/null +++ b/http2/http2_constants.h
@@ -0,0 +1,261 @@ +// 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_HTTP2_CONSTANTS_H_ +#define QUICHE_HTTP2_HTTP2_CONSTANTS_H_ + +// Constants from the HTTP/2 spec, RFC 7540, and associated helper functions. + +#include <cstdint> +#include <iosfwd> +#include <ostream> + +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" + +namespace http2 { + +// TODO(jamessynge): create http2_simple_types for types similar to +// SpdyStreamId, but not for structures like Http2FrameHeader. Then will be +// able to move these stream id functions there. +constexpr uint32_t UInt31Mask() { + return 0x7fffffff; +} +constexpr uint32_t StreamIdMask() { + return UInt31Mask(); +} + +// The value used to identify types of frames. Upper case to match the RFC. +// The comments indicate which flags are valid for that frame type. +// ALTSVC is defined in http://httpwg.org/http-extensions/alt-svc.html +// (not yet final standard as of March 2016, but close). +enum class Http2FrameType : uint8_t { + DATA = 0, // END_STREAM | PADDED + HEADERS = 1, // END_STREAM | END_HEADERS | PADDED | PRIORITY + PRIORITY = 2, // + RST_STREAM = 3, // + SETTINGS = 4, // ACK + PUSH_PROMISE = 5, // END_HEADERS | PADDED + PING = 6, // ACK + GOAWAY = 7, // + WINDOW_UPDATE = 8, // + CONTINUATION = 9, // END_HEADERS + ALTSVC = 10, // +}; + +// Is the frame type known/supported? +inline bool IsSupportedHttp2FrameType(uint32_t v) { + return v <= static_cast<uint32_t>(Http2FrameType::ALTSVC); +} +inline bool IsSupportedHttp2FrameType(Http2FrameType v) { + return IsSupportedHttp2FrameType(static_cast<uint32_t>(v)); +} + +// The return type is 'Http2String' so that they can generate a unique string +// for each unsupported value. Since these are just used for debugging/error +// messages, that isn't a cost to we need to worry about. The same applies to +// the functions later in this file. +HTTP2_EXPORT_PRIVATE Http2String Http2FrameTypeToString(Http2FrameType v); +HTTP2_EXPORT_PRIVATE Http2String Http2FrameTypeToString(uint8_t v); +HTTP2_EXPORT_PRIVATE inline std::ostream& operator<<(std::ostream& out, + Http2FrameType v) { + return out << Http2FrameTypeToString(v); +} + +// Flags that appear in supported frame types. These are treated as bit masks. +// The comments indicate for which frame types the flag is valid. +enum Http2FrameFlag { + END_STREAM = 0x01, // DATA, HEADERS + ACK = 0x01, // SETTINGS, PING + END_HEADERS = 0x04, // HEADERS, PUSH_PROMISE, CONTINUATION + PADDED = 0x08, // DATA, HEADERS, PUSH_PROMISE + PRIORITY = 0x20, // HEADERS +}; + +// Formats zero or more flags for the specified type of frame. Returns an +// empty string if flags==0. +HTTP2_EXPORT_PRIVATE Http2String Http2FrameFlagsToString(Http2FrameType type, + uint8_t flags); +HTTP2_EXPORT_PRIVATE Http2String Http2FrameFlagsToString(uint8_t type, + uint8_t flags); + +// Error codes for GOAWAY and RST_STREAM frames. +enum class Http2ErrorCode : uint32_t { + // The associated condition is not a result of an error. For example, a GOAWAY + // might include this code to indicate graceful shutdown of a connection. + HTTP2_NO_ERROR = 0x0, + + // The endpoint detected an unspecific protocol error. This error is for use + // when a more specific error code is not available. + PROTOCOL_ERROR = 0x1, + + // The endpoint encountered an unexpected internal error. + INTERNAL_ERROR = 0x2, + + // The endpoint detected that its peer violated the flow-control protocol. + FLOW_CONTROL_ERROR = 0x3, + + // The endpoint sent a SETTINGS frame but did not receive a response in a + // timely manner. See Section 6.5.3 ("Settings Synchronization"). + SETTINGS_TIMEOUT = 0x4, + + // The endpoint received a frame after a stream was half-closed. + STREAM_CLOSED = 0x5, + + // The endpoint received a frame with an invalid size. + FRAME_SIZE_ERROR = 0x6, + + // The endpoint refused the stream prior to performing any application + // processing (see Section 8.1.4 for details). + REFUSED_STREAM = 0x7, + + // Used by the endpoint to indicate that the stream is no longer needed. + CANCEL = 0x8, + + // The endpoint is unable to maintain the header compression context + // for the connection. + COMPRESSION_ERROR = 0x9, + + // The connection established in response to a CONNECT request (Section 8.3) + // was reset or abnormally closed. + CONNECT_ERROR = 0xa, + + // The endpoint detected that its peer is exhibiting a behavior that might + // be generating excessive load. + ENHANCE_YOUR_CALM = 0xb, + + // The underlying transport has properties that do not meet minimum + // security requirements (see Section 9.2). + INADEQUATE_SECURITY = 0xc, + + // The endpoint requires that HTTP/1.1 be used instead of HTTP/2. + HTTP_1_1_REQUIRED = 0xd, +}; + +// Is the error code supported? (So far that means it is in RFC 7540.) +inline bool IsSupportedHttp2ErrorCode(uint32_t v) { + return v <= static_cast<uint32_t>(Http2ErrorCode::HTTP_1_1_REQUIRED); +} +inline bool IsSupportedHttp2ErrorCode(Http2ErrorCode v) { + return IsSupportedHttp2ErrorCode(static_cast<uint32_t>(v)); +} + +// Format the specified error code. +HTTP2_EXPORT_PRIVATE Http2String Http2ErrorCodeToString(uint32_t v); +HTTP2_EXPORT_PRIVATE Http2String Http2ErrorCodeToString(Http2ErrorCode v); +HTTP2_EXPORT_PRIVATE inline std::ostream& operator<<(std::ostream& out, + Http2ErrorCode v) { + return out << Http2ErrorCodeToString(v); +} + +// Supported parameters in SETTINGS frames; so far just those in RFC 7540. +enum class Http2SettingsParameter : uint16_t { + // Allows the sender to inform the remote endpoint of the maximum size of the + // header compression table used to decode header blocks, in octets. The + // encoder can select any size equal to or less than this value by using + // signaling specific to the header compression format inside a header block + // (see [COMPRESSION]). The initial value is 4,096 octets. + HEADER_TABLE_SIZE = 0x1, + + // This setting can be used to disable server push (Section 8.2). An endpoint + // MUST NOT send a PUSH_PROMISE frame if it receives this parameter set to a + // value of 0. An endpoint that has both set this parameter to 0 and had it + // acknowledged MUST treat the receipt of a PUSH_PROMISE frame as a connection + // error (Section 5.4.1) of type PROTOCOL_ERROR. + // + // The initial value is 1, which indicates that server push is permitted. Any + // value other than 0 or 1 MUST be treated as a connection error (Section + // 5.4.1) of type PROTOCOL_ERROR. + ENABLE_PUSH = 0x2, + + // Indicates the maximum number of concurrent streams that the sender will + // allow. This limit is directional: it applies to the number of streams that + // the sender permits the receiver to create. Initially, there is no limit to + // this value. It is recommended that this value be no smaller than 100, so as + // to not unnecessarily limit parallelism. + // + // A value of 0 for MAX_CONCURRENT_STREAMS SHOULD NOT be treated as + // special by endpoints. A zero value does prevent the creation of new + // streams; however, this can also happen for any limit that is exhausted with + // active streams. Servers SHOULD only set a zero value for short durations; + // if a server does not wish to accept requests, closing the connection is + // more appropriate. + MAX_CONCURRENT_STREAMS = 0x3, + + // Indicates the sender's initial window size (in octets) for stream-level + // flow control. The initial value is 2^16-1 (65,535) octets. + // + // This setting affects the window size of all streams (see Section 6.9.2). + // + // Values above the maximum flow-control window size of 2^31-1 MUST be treated + // as a connection error (Section 5.4.1) of type FLOW_CONTROL_ERROR. + INITIAL_WINDOW_SIZE = 0x4, + + // Indicates the size of the largest frame payload that the sender is willing + // to receive, in octets. + // + // The initial value is 2^14 (16,384) octets. The value advertised by an + // endpoint MUST be between this initial value and the maximum allowed frame + // size (2^24-1 or 16,777,215 octets), inclusive. Values outside this range + // MUST be treated as a connection error (Section 5.4.1) of type + // PROTOCOL_ERROR. + MAX_FRAME_SIZE = 0x5, + + // This advisory setting informs a peer of the maximum size of header list + // that the sender is prepared to accept, in octets. The value is based on the + // uncompressed size of header fields, including the length of the name and + // value in octets plus an overhead of 32 octets for each header field. + // + // For any given request, a lower limit than what is advertised MAY be + // enforced. The initial value of this setting is unlimited. + MAX_HEADER_LIST_SIZE = 0x6, +}; + +// Is the settings parameter supported (so far that means it is in RFC 7540)? +inline bool IsSupportedHttp2SettingsParameter(uint32_t v) { + return 0 < v && v <= static_cast<uint32_t>( + Http2SettingsParameter::MAX_HEADER_LIST_SIZE); +} +inline bool IsSupportedHttp2SettingsParameter(Http2SettingsParameter v) { + return IsSupportedHttp2SettingsParameter(static_cast<uint32_t>(v)); +} + +// Format the specified settings parameter. +HTTP2_EXPORT_PRIVATE Http2String Http2SettingsParameterToString(uint32_t v); +HTTP2_EXPORT_PRIVATE Http2String +Http2SettingsParameterToString(Http2SettingsParameter v); +inline std::ostream& operator<<(std::ostream& out, Http2SettingsParameter v) { + return out << Http2SettingsParameterToString(v); +} + +// Information about the initial, minimum and maximum value of settings (not +// applicable to all settings parameters). +class Http2SettingsInfo { + public: + // Default value for HEADER_TABLE_SIZE. + static constexpr uint32_t DefaultHeaderTableSize() { return 4096; } + + // Default value for ENABLE_PUSH. + static constexpr bool DefaultEnablePush() { return true; } + + // Default value for INITIAL_WINDOW_SIZE. + static constexpr uint32_t DefaultInitialWindowSize() { return 65535; } + + // Maximum value for INITIAL_WINDOW_SIZE, and for the connection flow control + // window, and for each stream flow control window. + static constexpr uint32_t MaximumWindowSize() { return UInt31Mask(); } + + // Default value for MAX_FRAME_SIZE. + static constexpr uint32_t DefaultMaxFrameSize() { return 16384; } + + // Minimum value for MAX_FRAME_SIZE. + static constexpr uint32_t MinimumMaxFrameSize() { return 16384; } + + // Maximum value for MAX_FRAME_SIZE. + static constexpr uint32_t MaximumMaxFrameSize() { return (1 << 24) - 1; } +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HTTP2_CONSTANTS_H_
diff --git a/http2/http2_constants_test.cc b/http2/http2_constants_test.cc new file mode 100644 index 0000000..223082a --- /dev/null +++ b/http2/http2_constants_test.cc
@@ -0,0 +1,271 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/http2_constants.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace http2 { +namespace test { +namespace { + +class Http2ConstantsTest : public testing::Test {}; + +TEST(Http2ConstantsTest, Http2FrameType) { + EXPECT_EQ(Http2FrameType::DATA, static_cast<Http2FrameType>(0)); + EXPECT_EQ(Http2FrameType::HEADERS, static_cast<Http2FrameType>(1)); + EXPECT_EQ(Http2FrameType::PRIORITY, static_cast<Http2FrameType>(2)); + EXPECT_EQ(Http2FrameType::RST_STREAM, static_cast<Http2FrameType>(3)); + EXPECT_EQ(Http2FrameType::SETTINGS, static_cast<Http2FrameType>(4)); + EXPECT_EQ(Http2FrameType::PUSH_PROMISE, static_cast<Http2FrameType>(5)); + EXPECT_EQ(Http2FrameType::PING, static_cast<Http2FrameType>(6)); + EXPECT_EQ(Http2FrameType::GOAWAY, static_cast<Http2FrameType>(7)); + EXPECT_EQ(Http2FrameType::WINDOW_UPDATE, static_cast<Http2FrameType>(8)); + EXPECT_EQ(Http2FrameType::CONTINUATION, static_cast<Http2FrameType>(9)); + EXPECT_EQ(Http2FrameType::ALTSVC, static_cast<Http2FrameType>(10)); +} + +TEST(Http2ConstantsTest, Http2FrameTypeToString) { + EXPECT_EQ("DATA", Http2FrameTypeToString(Http2FrameType::DATA)); + EXPECT_EQ("HEADERS", Http2FrameTypeToString(Http2FrameType::HEADERS)); + EXPECT_EQ("PRIORITY", Http2FrameTypeToString(Http2FrameType::PRIORITY)); + EXPECT_EQ("RST_STREAM", Http2FrameTypeToString(Http2FrameType::RST_STREAM)); + EXPECT_EQ("SETTINGS", Http2FrameTypeToString(Http2FrameType::SETTINGS)); + EXPECT_EQ("PUSH_PROMISE", + Http2FrameTypeToString(Http2FrameType::PUSH_PROMISE)); + EXPECT_EQ("PING", Http2FrameTypeToString(Http2FrameType::PING)); + EXPECT_EQ("GOAWAY", Http2FrameTypeToString(Http2FrameType::GOAWAY)); + EXPECT_EQ("WINDOW_UPDATE", + Http2FrameTypeToString(Http2FrameType::WINDOW_UPDATE)); + EXPECT_EQ("CONTINUATION", + Http2FrameTypeToString(Http2FrameType::CONTINUATION)); + EXPECT_EQ("ALTSVC", Http2FrameTypeToString(Http2FrameType::ALTSVC)); + + EXPECT_EQ("DATA", Http2FrameTypeToString(0)); + EXPECT_EQ("HEADERS", Http2FrameTypeToString(1)); + EXPECT_EQ("PRIORITY", Http2FrameTypeToString(2)); + EXPECT_EQ("RST_STREAM", Http2FrameTypeToString(3)); + EXPECT_EQ("SETTINGS", Http2FrameTypeToString(4)); + EXPECT_EQ("PUSH_PROMISE", Http2FrameTypeToString(5)); + EXPECT_EQ("PING", Http2FrameTypeToString(6)); + EXPECT_EQ("GOAWAY", Http2FrameTypeToString(7)); + EXPECT_EQ("WINDOW_UPDATE", Http2FrameTypeToString(8)); + EXPECT_EQ("CONTINUATION", Http2FrameTypeToString(9)); + EXPECT_EQ("ALTSVC", Http2FrameTypeToString(10)); + + EXPECT_EQ("UnknownFrameType(99)", Http2FrameTypeToString(99)); +} + +TEST(Http2ConstantsTest, Http2FrameFlag) { + EXPECT_EQ(Http2FrameFlag::END_STREAM, static_cast<Http2FrameFlag>(0x01)); + EXPECT_EQ(Http2FrameFlag::ACK, static_cast<Http2FrameFlag>(0x01)); + EXPECT_EQ(Http2FrameFlag::END_HEADERS, static_cast<Http2FrameFlag>(0x04)); + EXPECT_EQ(Http2FrameFlag::PADDED, static_cast<Http2FrameFlag>(0x08)); + EXPECT_EQ(Http2FrameFlag::PRIORITY, static_cast<Http2FrameFlag>(0x20)); + + EXPECT_EQ(Http2FrameFlag::END_STREAM, 0x01); + EXPECT_EQ(Http2FrameFlag::ACK, 0x01); + EXPECT_EQ(Http2FrameFlag::END_HEADERS, 0x04); + EXPECT_EQ(Http2FrameFlag::PADDED, 0x08); + EXPECT_EQ(Http2FrameFlag::PRIORITY, 0x20); +} + +TEST(Http2ConstantsTest, Http2FrameFlagsToString) { + // Single flags... + + // 0b00000001 + EXPECT_EQ("END_STREAM", Http2FrameFlagsToString(Http2FrameType::DATA, + Http2FrameFlag::END_STREAM)); + EXPECT_EQ("END_STREAM", + Http2FrameFlagsToString(Http2FrameType::HEADERS, 0x01)); + EXPECT_EQ("ACK", Http2FrameFlagsToString(Http2FrameType::SETTINGS, + Http2FrameFlag::ACK)); + EXPECT_EQ("ACK", Http2FrameFlagsToString(Http2FrameType::PING, 0x01)); + + // 0b00000010 + EXPECT_EQ("0x02", Http2FrameFlagsToString(0xff, 0x02)); + + // 0b00000100 + EXPECT_EQ("END_HEADERS", + Http2FrameFlagsToString(Http2FrameType::HEADERS, + Http2FrameFlag::END_HEADERS)); + EXPECT_EQ("END_HEADERS", + Http2FrameFlagsToString(Http2FrameType::PUSH_PROMISE, 0x04)); + EXPECT_EQ("END_HEADERS", Http2FrameFlagsToString(0x09, 0x04)); + EXPECT_EQ("0x04", Http2FrameFlagsToString(0xff, 0x04)); + + // 0b00001000 + EXPECT_EQ("PADDED", Http2FrameFlagsToString(Http2FrameType::DATA, + Http2FrameFlag::PADDED)); + EXPECT_EQ("PADDED", Http2FrameFlagsToString(Http2FrameType::HEADERS, 0x08)); + EXPECT_EQ("PADDED", Http2FrameFlagsToString(0x05, 0x08)); + EXPECT_EQ("0x08", Http2FrameFlagsToString(0xff, Http2FrameFlag::PADDED)); + + // 0b00010000 + EXPECT_EQ("0x10", Http2FrameFlagsToString(Http2FrameType::SETTINGS, 0x10)); + + // 0b00100000 + EXPECT_EQ("PRIORITY", Http2FrameFlagsToString(Http2FrameType::HEADERS, 0x20)); + EXPECT_EQ("0x20", + Http2FrameFlagsToString(Http2FrameType::PUSH_PROMISE, 0x20)); + + // 0b01000000 + EXPECT_EQ("0x40", Http2FrameFlagsToString(0xff, 0x40)); + + // 0b10000000 + EXPECT_EQ("0x80", Http2FrameFlagsToString(0xff, 0x80)); + + // Combined flags... + + EXPECT_EQ("END_STREAM|PADDED|0xf6", + Http2FrameFlagsToString(Http2FrameType::DATA, 0xff)); + EXPECT_EQ("END_STREAM|END_HEADERS|PADDED|PRIORITY|0xd2", + Http2FrameFlagsToString(Http2FrameType::HEADERS, 0xff)); + EXPECT_EQ("0xff", Http2FrameFlagsToString(Http2FrameType::PRIORITY, 0xff)); + EXPECT_EQ("0xff", Http2FrameFlagsToString(Http2FrameType::RST_STREAM, 0xff)); + EXPECT_EQ("ACK|0xfe", + Http2FrameFlagsToString(Http2FrameType::SETTINGS, 0xff)); + EXPECT_EQ("END_HEADERS|PADDED|0xf3", + Http2FrameFlagsToString(Http2FrameType::PUSH_PROMISE, 0xff)); + EXPECT_EQ("ACK|0xfe", Http2FrameFlagsToString(Http2FrameType::PING, 0xff)); + EXPECT_EQ("0xff", Http2FrameFlagsToString(Http2FrameType::GOAWAY, 0xff)); + EXPECT_EQ("0xff", + Http2FrameFlagsToString(Http2FrameType::WINDOW_UPDATE, 0xff)); + EXPECT_EQ("END_HEADERS|0xfb", + Http2FrameFlagsToString(Http2FrameType::CONTINUATION, 0xff)); + EXPECT_EQ("0xff", Http2FrameFlagsToString(Http2FrameType::ALTSVC, 0xff)); + EXPECT_EQ("0xff", Http2FrameFlagsToString(0xff, 0xff)); +} + +TEST(Http2ConstantsTest, Http2ErrorCode) { + EXPECT_EQ(Http2ErrorCode::HTTP2_NO_ERROR, static_cast<Http2ErrorCode>(0x0)); + EXPECT_EQ(Http2ErrorCode::PROTOCOL_ERROR, static_cast<Http2ErrorCode>(0x1)); + EXPECT_EQ(Http2ErrorCode::INTERNAL_ERROR, static_cast<Http2ErrorCode>(0x2)); + EXPECT_EQ(Http2ErrorCode::FLOW_CONTROL_ERROR, + static_cast<Http2ErrorCode>(0x3)); + EXPECT_EQ(Http2ErrorCode::SETTINGS_TIMEOUT, static_cast<Http2ErrorCode>(0x4)); + EXPECT_EQ(Http2ErrorCode::STREAM_CLOSED, static_cast<Http2ErrorCode>(0x5)); + EXPECT_EQ(Http2ErrorCode::FRAME_SIZE_ERROR, static_cast<Http2ErrorCode>(0x6)); + EXPECT_EQ(Http2ErrorCode::REFUSED_STREAM, static_cast<Http2ErrorCode>(0x7)); + EXPECT_EQ(Http2ErrorCode::CANCEL, static_cast<Http2ErrorCode>(0x8)); + EXPECT_EQ(Http2ErrorCode::COMPRESSION_ERROR, + static_cast<Http2ErrorCode>(0x9)); + EXPECT_EQ(Http2ErrorCode::CONNECT_ERROR, static_cast<Http2ErrorCode>(0xa)); + EXPECT_EQ(Http2ErrorCode::ENHANCE_YOUR_CALM, + static_cast<Http2ErrorCode>(0xb)); + EXPECT_EQ(Http2ErrorCode::INADEQUATE_SECURITY, + static_cast<Http2ErrorCode>(0xc)); + EXPECT_EQ(Http2ErrorCode::HTTP_1_1_REQUIRED, + static_cast<Http2ErrorCode>(0xd)); +} + +TEST(Http2ConstantsTest, Http2ErrorCodeToString) { + EXPECT_EQ("NO_ERROR", Http2ErrorCodeToString(Http2ErrorCode::HTTP2_NO_ERROR)); + EXPECT_EQ("NO_ERROR", Http2ErrorCodeToString(0x0)); + EXPECT_EQ("PROTOCOL_ERROR", + Http2ErrorCodeToString(Http2ErrorCode::PROTOCOL_ERROR)); + EXPECT_EQ("PROTOCOL_ERROR", Http2ErrorCodeToString(0x1)); + EXPECT_EQ("INTERNAL_ERROR", + Http2ErrorCodeToString(Http2ErrorCode::INTERNAL_ERROR)); + EXPECT_EQ("INTERNAL_ERROR", Http2ErrorCodeToString(0x2)); + EXPECT_EQ("FLOW_CONTROL_ERROR", + Http2ErrorCodeToString(Http2ErrorCode::FLOW_CONTROL_ERROR)); + EXPECT_EQ("FLOW_CONTROL_ERROR", Http2ErrorCodeToString(0x3)); + EXPECT_EQ("SETTINGS_TIMEOUT", + Http2ErrorCodeToString(Http2ErrorCode::SETTINGS_TIMEOUT)); + EXPECT_EQ("SETTINGS_TIMEOUT", Http2ErrorCodeToString(0x4)); + EXPECT_EQ("STREAM_CLOSED", + Http2ErrorCodeToString(Http2ErrorCode::STREAM_CLOSED)); + EXPECT_EQ("STREAM_CLOSED", Http2ErrorCodeToString(0x5)); + EXPECT_EQ("FRAME_SIZE_ERROR", + Http2ErrorCodeToString(Http2ErrorCode::FRAME_SIZE_ERROR)); + EXPECT_EQ("FRAME_SIZE_ERROR", Http2ErrorCodeToString(0x6)); + EXPECT_EQ("REFUSED_STREAM", + Http2ErrorCodeToString(Http2ErrorCode::REFUSED_STREAM)); + EXPECT_EQ("REFUSED_STREAM", Http2ErrorCodeToString(0x7)); + EXPECT_EQ("CANCEL", Http2ErrorCodeToString(Http2ErrorCode::CANCEL)); + EXPECT_EQ("CANCEL", Http2ErrorCodeToString(0x8)); + EXPECT_EQ("COMPRESSION_ERROR", + Http2ErrorCodeToString(Http2ErrorCode::COMPRESSION_ERROR)); + EXPECT_EQ("COMPRESSION_ERROR", Http2ErrorCodeToString(0x9)); + EXPECT_EQ("CONNECT_ERROR", + Http2ErrorCodeToString(Http2ErrorCode::CONNECT_ERROR)); + EXPECT_EQ("CONNECT_ERROR", Http2ErrorCodeToString(0xa)); + EXPECT_EQ("ENHANCE_YOUR_CALM", + Http2ErrorCodeToString(Http2ErrorCode::ENHANCE_YOUR_CALM)); + EXPECT_EQ("ENHANCE_YOUR_CALM", Http2ErrorCodeToString(0xb)); + EXPECT_EQ("INADEQUATE_SECURITY", + Http2ErrorCodeToString(Http2ErrorCode::INADEQUATE_SECURITY)); + EXPECT_EQ("INADEQUATE_SECURITY", Http2ErrorCodeToString(0xc)); + EXPECT_EQ("HTTP_1_1_REQUIRED", + Http2ErrorCodeToString(Http2ErrorCode::HTTP_1_1_REQUIRED)); + EXPECT_EQ("HTTP_1_1_REQUIRED", Http2ErrorCodeToString(0xd)); + + EXPECT_EQ("UnknownErrorCode(0x123)", Http2ErrorCodeToString(0x123)); +} + +TEST(Http2ConstantsTest, Http2SettingsParameter) { + EXPECT_EQ(Http2SettingsParameter::HEADER_TABLE_SIZE, + static_cast<Http2SettingsParameter>(0x1)); + EXPECT_EQ(Http2SettingsParameter::ENABLE_PUSH, + static_cast<Http2SettingsParameter>(0x2)); + EXPECT_EQ(Http2SettingsParameter::MAX_CONCURRENT_STREAMS, + static_cast<Http2SettingsParameter>(0x3)); + EXPECT_EQ(Http2SettingsParameter::INITIAL_WINDOW_SIZE, + static_cast<Http2SettingsParameter>(0x4)); + EXPECT_EQ(Http2SettingsParameter::MAX_FRAME_SIZE, + static_cast<Http2SettingsParameter>(0x5)); + EXPECT_EQ(Http2SettingsParameter::MAX_HEADER_LIST_SIZE, + static_cast<Http2SettingsParameter>(0x6)); + + EXPECT_TRUE(IsSupportedHttp2SettingsParameter( + Http2SettingsParameter::HEADER_TABLE_SIZE)); + EXPECT_TRUE( + IsSupportedHttp2SettingsParameter(Http2SettingsParameter::ENABLE_PUSH)); + EXPECT_TRUE(IsSupportedHttp2SettingsParameter( + Http2SettingsParameter::MAX_CONCURRENT_STREAMS)); + EXPECT_TRUE(IsSupportedHttp2SettingsParameter( + Http2SettingsParameter::INITIAL_WINDOW_SIZE)); + EXPECT_TRUE(IsSupportedHttp2SettingsParameter( + Http2SettingsParameter::MAX_FRAME_SIZE)); + EXPECT_TRUE(IsSupportedHttp2SettingsParameter( + Http2SettingsParameter::MAX_HEADER_LIST_SIZE)); + + EXPECT_FALSE(IsSupportedHttp2SettingsParameter( + static_cast<Http2SettingsParameter>(0))); + EXPECT_FALSE(IsSupportedHttp2SettingsParameter( + static_cast<Http2SettingsParameter>(7))); +} + +TEST(Http2ConstantsTest, Http2SettingsParameterToString) { + EXPECT_EQ("HEADER_TABLE_SIZE", + Http2SettingsParameterToString( + Http2SettingsParameter::HEADER_TABLE_SIZE)); + EXPECT_EQ("HEADER_TABLE_SIZE", Http2SettingsParameterToString(0x1)); + EXPECT_EQ("ENABLE_PUSH", Http2SettingsParameterToString( + Http2SettingsParameter::ENABLE_PUSH)); + EXPECT_EQ("ENABLE_PUSH", Http2SettingsParameterToString(0x2)); + EXPECT_EQ("MAX_CONCURRENT_STREAMS", + Http2SettingsParameterToString( + Http2SettingsParameter::MAX_CONCURRENT_STREAMS)); + EXPECT_EQ("MAX_CONCURRENT_STREAMS", Http2SettingsParameterToString(0x3)); + EXPECT_EQ("INITIAL_WINDOW_SIZE", + Http2SettingsParameterToString( + Http2SettingsParameter::INITIAL_WINDOW_SIZE)); + EXPECT_EQ("INITIAL_WINDOW_SIZE", Http2SettingsParameterToString(0x4)); + EXPECT_EQ("MAX_FRAME_SIZE", Http2SettingsParameterToString( + Http2SettingsParameter::MAX_FRAME_SIZE)); + EXPECT_EQ("MAX_FRAME_SIZE", Http2SettingsParameterToString(0x5)); + EXPECT_EQ("MAX_HEADER_LIST_SIZE", + Http2SettingsParameterToString( + Http2SettingsParameter::MAX_HEADER_LIST_SIZE)); + EXPECT_EQ("MAX_HEADER_LIST_SIZE", Http2SettingsParameterToString(0x6)); + + EXPECT_EQ("UnknownSettingsParameter(0x123)", + Http2SettingsParameterToString(0x123)); +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/http2_constants_test_util.cc b/http2/http2_constants_test_util.cc new file mode 100644 index 0000000..e729890 --- /dev/null +++ b/http2/http2_constants_test_util.cc
@@ -0,0 +1,84 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/http2_constants_test_util.h" + +namespace http2 { +namespace test { + +std::vector<Http2ErrorCode> AllHttp2ErrorCodes() { + // clang-format off + return { + Http2ErrorCode::HTTP2_NO_ERROR, + Http2ErrorCode::PROTOCOL_ERROR, + Http2ErrorCode::INTERNAL_ERROR, + Http2ErrorCode::FLOW_CONTROL_ERROR, + Http2ErrorCode::SETTINGS_TIMEOUT, + Http2ErrorCode::STREAM_CLOSED, + Http2ErrorCode::FRAME_SIZE_ERROR, + Http2ErrorCode::REFUSED_STREAM, + Http2ErrorCode::CANCEL, + Http2ErrorCode::COMPRESSION_ERROR, + Http2ErrorCode::CONNECT_ERROR, + Http2ErrorCode::ENHANCE_YOUR_CALM, + Http2ErrorCode::INADEQUATE_SECURITY, + Http2ErrorCode::HTTP_1_1_REQUIRED, + }; + // clang-format on +} + +std::vector<Http2SettingsParameter> AllHttp2SettingsParameters() { + // clang-format off + return { + Http2SettingsParameter::HEADER_TABLE_SIZE, + Http2SettingsParameter::ENABLE_PUSH, + Http2SettingsParameter::MAX_CONCURRENT_STREAMS, + Http2SettingsParameter::INITIAL_WINDOW_SIZE, + Http2SettingsParameter::MAX_FRAME_SIZE, + Http2SettingsParameter::MAX_HEADER_LIST_SIZE, + }; + // clang-format on +} + +// Returns a mask of flags supported for the specified frame type. Returns +// zero for unknown frame types. +uint8_t KnownFlagsMaskForFrameType(Http2FrameType type) { + switch (type) { + case Http2FrameType::DATA: + return Http2FrameFlag::END_STREAM | Http2FrameFlag::PADDED; + case Http2FrameType::HEADERS: + return Http2FrameFlag::END_STREAM | Http2FrameFlag::END_HEADERS | + Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY; + case Http2FrameType::PRIORITY: + return 0x00; + case Http2FrameType::RST_STREAM: + return 0x00; + case Http2FrameType::SETTINGS: + return Http2FrameFlag::ACK; + case Http2FrameType::PUSH_PROMISE: + return Http2FrameFlag::END_HEADERS | Http2FrameFlag::PADDED; + case Http2FrameType::PING: + return Http2FrameFlag::ACK; + case Http2FrameType::GOAWAY: + return 0x00; + case Http2FrameType::WINDOW_UPDATE: + return 0x00; + case Http2FrameType::CONTINUATION: + return Http2FrameFlag::END_HEADERS; + case Http2FrameType::ALTSVC: + return 0x00; + default: + return 0x00; + } +} + +uint8_t InvalidFlagMaskForFrameType(Http2FrameType type) { + if (IsSupportedHttp2FrameType(type)) { + return ~KnownFlagsMaskForFrameType(type); + } + return 0x00; +} + +} // namespace test +} // namespace http2
diff --git a/http2/http2_constants_test_util.h b/http2/http2_constants_test_util.h new file mode 100644 index 0000000..6ddc1cd --- /dev/null +++ b/http2/http2_constants_test_util.h
@@ -0,0 +1,34 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HTTP2_CONSTANTS_TEST_UTIL_H_ +#define QUICHE_HTTP2_HTTP2_CONSTANTS_TEST_UTIL_H_ + +#include <cstdint> +#include <vector> + +#include "net/third_party/quiche/src/http2/http2_constants.h" + +namespace http2 { +namespace test { + +// Returns a vector of all supported RST_STREAM and GOAWAY error codes. +std::vector<Http2ErrorCode> AllHttp2ErrorCodes(); + +// Returns a vector of all supported parameters in SETTINGS frames. +std::vector<Http2SettingsParameter> AllHttp2SettingsParameters(); + +// Returns a mask of flags supported for the specified frame type. Returns +// zero for unknown frame types. +uint8_t KnownFlagsMaskForFrameType(Http2FrameType type); + +// Returns a mask of flag bits known to be invalid for the frame type. +// For unknown frame types, the mask is zero; i.e., we don't know that any +// are invalid. +uint8_t InvalidFlagMaskForFrameType(Http2FrameType type); + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_HTTP2_CONSTANTS_TEST_UTIL_H_
diff --git a/http2/http2_structures.cc b/http2/http2_structures.cc new file mode 100644 index 0000000..7dbaf30 --- /dev/null +++ b/http2/http2_structures.cc
@@ -0,0 +1,132 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/http2_structures.h" + +#include <cstring> // For std::memcmp +#include <sstream> + +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { + +// Http2FrameHeader: + +bool Http2FrameHeader::IsProbableHttpResponse() const { + return (payload_length == 0x485454 && // "HTT" + static_cast<char>(type) == 'P' && // "P" + flags == '/'); // "/" +} + +Http2String Http2FrameHeader::ToString() const { + return Http2StrCat("length=", payload_length, + ", type=", Http2FrameTypeToString(type), + ", flags=", FlagsToString(), ", stream=", stream_id); +} + +Http2String Http2FrameHeader::FlagsToString() const { + return Http2FrameFlagsToString(type, flags); +} + +bool operator==(const Http2FrameHeader& a, const Http2FrameHeader& b) { + return a.payload_length == b.payload_length && a.stream_id == b.stream_id && + a.type == b.type && a.flags == b.flags; +} + +std::ostream& operator<<(std::ostream& out, const Http2FrameHeader& v) { + return out << v.ToString(); +} + +// Http2PriorityFields: + +bool operator==(const Http2PriorityFields& a, const Http2PriorityFields& b) { + return a.stream_dependency == b.stream_dependency && a.weight == b.weight; +} + +Http2String Http2PriorityFields::ToString() const { + std::stringstream ss; + ss << "E=" << (is_exclusive ? "true" : "false") + << ", stream=" << stream_dependency + << ", weight=" << static_cast<uint32_t>(weight); + return ss.str(); +} + +std::ostream& operator<<(std::ostream& out, const Http2PriorityFields& v) { + return out << v.ToString(); +} + +// Http2RstStreamFields: + +bool operator==(const Http2RstStreamFields& a, const Http2RstStreamFields& b) { + return a.error_code == b.error_code; +} + +std::ostream& operator<<(std::ostream& out, const Http2RstStreamFields& v) { + return out << "error_code=" << v.error_code; +} + +// Http2SettingFields: + +bool operator==(const Http2SettingFields& a, const Http2SettingFields& b) { + return a.parameter == b.parameter && a.value == b.value; +} +std::ostream& operator<<(std::ostream& out, const Http2SettingFields& v) { + return out << "parameter=" << v.parameter << ", value=" << v.value; +} + +// Http2PushPromiseFields: + +bool operator==(const Http2PushPromiseFields& a, + const Http2PushPromiseFields& b) { + return a.promised_stream_id == b.promised_stream_id; +} + +std::ostream& operator<<(std::ostream& out, const Http2PushPromiseFields& v) { + return out << "promised_stream_id=" << v.promised_stream_id; +} + +// Http2PingFields: + +bool operator==(const Http2PingFields& a, const Http2PingFields& b) { + static_assert((sizeof a.opaque_bytes) == Http2PingFields::EncodedSize(), + "Why not the same size?"); + return 0 == + std::memcmp(a.opaque_bytes, b.opaque_bytes, sizeof a.opaque_bytes); +} + +std::ostream& operator<<(std::ostream& out, const Http2PingFields& v) { + return out << "opaque_bytes=0x" + << Http2HexEncode(v.opaque_bytes, sizeof v.opaque_bytes); +} + +// Http2GoAwayFields: + +bool operator==(const Http2GoAwayFields& a, const Http2GoAwayFields& b) { + return a.last_stream_id == b.last_stream_id && a.error_code == b.error_code; +} +std::ostream& operator<<(std::ostream& out, const Http2GoAwayFields& v) { + return out << "last_stream_id=" << v.last_stream_id + << ", error_code=" << v.error_code; +} + +// Http2WindowUpdateFields: + +bool operator==(const Http2WindowUpdateFields& a, + const Http2WindowUpdateFields& b) { + return a.window_size_increment == b.window_size_increment; +} +std::ostream& operator<<(std::ostream& out, const Http2WindowUpdateFields& v) { + return out << "window_size_increment=" << v.window_size_increment; +} + +// Http2AltSvcFields: + +bool operator==(const Http2AltSvcFields& a, const Http2AltSvcFields& b) { + return a.origin_length == b.origin_length; +} +std::ostream& operator<<(std::ostream& out, const Http2AltSvcFields& v) { + return out << "origin_length=" << v.origin_length; +} + +} // namespace http2
diff --git a/http2/http2_structures.h b/http2/http2_structures.h new file mode 100644 index 0000000..01c011e --- /dev/null +++ b/http2/http2_structures.h
@@ -0,0 +1,325 @@ +// 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_HTTP2_STRUCTURES_H_ +#define QUICHE_HTTP2_HTTP2_STRUCTURES_H_ + +// Defines structs for various fixed sized structures in HTTP/2. +// +// Those structs with multiple fields have constructors that take arguments in +// the same order as their encoding (which may be different from their order +// in the struct). For single field structs, use aggregate initialization if +// desired, e.g.: +// +// Http2RstStreamFields var{Http2ErrorCode::ENHANCE_YOUR_CALM}; +// or: +// SomeFunc(Http2RstStreamFields{Http2ErrorCode::ENHANCE_YOUR_CALM}); +// +// Each struct includes a static method EncodedSize which returns the number +// of bytes of the encoding. +// +// With the exception of Http2FrameHeader, all the types are named +// Http2<X>Fields, where X is the title-case form of the frame which always +// includes the fields; the "always" is to cover the case of the PRIORITY frame; +// its fields optionally appear in the HEADERS frame, but the struct is called +// Http2PriorityFields. + +#include <stddef.h> + +#include <cstdint> +#include <ostream> + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" + +namespace http2 { + +struct HTTP2_EXPORT_PRIVATE Http2FrameHeader { + Http2FrameHeader() {} + Http2FrameHeader(uint32_t payload_length, + Http2FrameType type, + uint8_t flags, + uint32_t stream_id) + : payload_length(payload_length), + stream_id(stream_id), + type(type), + flags(static_cast<Http2FrameFlag>(flags)) { + DCHECK_LT(payload_length, static_cast<uint32_t>(1 << 24)) + << "Payload Length is only a 24 bit field\n" + << ToString(); + } + + static constexpr size_t EncodedSize() { return 9; } + + // Keep the current value of those flags that are in + // valid_flags, and clear all the others. + void RetainFlags(uint8_t valid_flags) { + flags = static_cast<Http2FrameFlag>(flags & valid_flags); + } + + // Returns true if any of the flags in flag_mask are set, + // otherwise false. + bool HasAnyFlags(uint8_t flag_mask) const { return 0 != (flags & flag_mask); } + + // Is the END_STREAM flag set? + bool IsEndStream() const { + DCHECK(type == Http2FrameType::DATA || type == Http2FrameType::HEADERS) + << ToString(); + return (flags & Http2FrameFlag::END_STREAM) != 0; + } + + // Is the ACK flag set? + bool IsAck() const { + DCHECK(type == Http2FrameType::SETTINGS || type == Http2FrameType::PING) + << ToString(); + return (flags & Http2FrameFlag::ACK) != 0; + } + + // Is the END_HEADERS flag set? + bool IsEndHeaders() const { + DCHECK(type == Http2FrameType::HEADERS || + type == Http2FrameType::PUSH_PROMISE || + type == Http2FrameType::CONTINUATION) + << ToString(); + return (flags & Http2FrameFlag::END_HEADERS) != 0; + } + + // Is the PADDED flag set? + bool IsPadded() const { + DCHECK(type == Http2FrameType::DATA || type == Http2FrameType::HEADERS || + type == Http2FrameType::PUSH_PROMISE) + << ToString(); + return (flags & Http2FrameFlag::PADDED) != 0; + } + + // Is the PRIORITY flag set? + bool HasPriority() const { + DCHECK_EQ(type, Http2FrameType::HEADERS) << ToString(); + return (flags & Http2FrameFlag::PRIORITY) != 0; + } + + // Does the encoding of this header start with "HTTP/", indicating that it + // might be from a non-HTTP/2 server. + bool IsProbableHttpResponse() const; + + // Produce strings useful for debugging/logging messages. + Http2String ToString() const; + Http2String FlagsToString() const; + + // 24 bit length of the payload after the header, including any padding. + // First field in encoding. + uint32_t payload_length; // 24 bits + + // 31 bit stream id, with high bit (32nd bit) reserved (must be zero), + // and is cleared during decoding. + // Fourth field in encoding. + uint32_t stream_id; + + // Type of the frame. + // Second field in encoding. + Http2FrameType type; + + // Flag bits, with interpretations that depend upon the frame type. + // Flag bits not used by the frame type are cleared. + // Third field in encoding. + Http2FrameFlag flags; +}; + +HTTP2_EXPORT_PRIVATE bool operator==(const Http2FrameHeader& a, + const Http2FrameHeader& b); +HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2FrameHeader& a, + const Http2FrameHeader& b) { + return !(a == b); +} +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2FrameHeader& v); + +// Http2PriorityFields: + +struct HTTP2_EXPORT_PRIVATE Http2PriorityFields { + Http2PriorityFields() {} + Http2PriorityFields(uint32_t stream_dependency, + uint32_t weight, + bool is_exclusive) + : stream_dependency(stream_dependency), + weight(weight), + is_exclusive(is_exclusive) { + // Can't have the high-bit set in the stream id because we need to use + // that for the EXCLUSIVE flag bit. + DCHECK_EQ(stream_dependency, stream_dependency & StreamIdMask()) + << "Stream Dependency is only a 31-bit field.\n" + << ToString(); + DCHECK_LE(1u, weight) << "Weight is too small."; + DCHECK_LE(weight, 256u) << "Weight is too large."; + } + static constexpr size_t EncodedSize() { return 5; } + + // Produce strings useful for debugging/logging messages. + Http2String ToString() const; + + // A 31-bit stream identifier for the stream that this stream depends on. + uint32_t stream_dependency; + + // Weight (1 to 256) is encoded as a byte in the range 0 to 255, so we + // add one when decoding, and store it in a field larger than a byte. + uint32_t weight; + + // A single-bit flag indicating that the stream dependency is exclusive; + // extracted from high bit of stream dependency field during decoding. + bool is_exclusive; +}; + +HTTP2_EXPORT_PRIVATE bool operator==(const Http2PriorityFields& a, + const Http2PriorityFields& b); +HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2PriorityFields& a, + const Http2PriorityFields& b) { + return !(a == b); +} +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2PriorityFields& v); + +// Http2RstStreamFields: + +struct Http2RstStreamFields { + static constexpr size_t EncodedSize() { return 4; } + bool IsSupportedErrorCode() const { + return IsSupportedHttp2ErrorCode(error_code); + } + + Http2ErrorCode error_code; +}; + +HTTP2_EXPORT_PRIVATE bool operator==(const Http2RstStreamFields& a, + const Http2RstStreamFields& b); +HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2RstStreamFields& a, + const Http2RstStreamFields& b) { + return !(a == b); +} +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2RstStreamFields& v); + +// Http2SettingFields: + +struct Http2SettingFields { + Http2SettingFields() {} + Http2SettingFields(Http2SettingsParameter parameter, uint32_t value) + : parameter(parameter), value(value) {} + static constexpr size_t EncodedSize() { return 6; } + bool IsSupportedParameter() const { + return IsSupportedHttp2SettingsParameter(parameter); + } + + Http2SettingsParameter parameter; + uint32_t value; +}; + +HTTP2_EXPORT_PRIVATE bool operator==(const Http2SettingFields& a, + const Http2SettingFields& b); +HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2SettingFields& a, + const Http2SettingFields& b) { + return !(a == b); +} +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2SettingFields& v); + +// Http2PushPromiseFields: + +struct Http2PushPromiseFields { + static constexpr size_t EncodedSize() { return 4; } + + uint32_t promised_stream_id; +}; + +HTTP2_EXPORT_PRIVATE bool operator==(const Http2PushPromiseFields& a, + const Http2PushPromiseFields& b); +HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2PushPromiseFields& a, + const Http2PushPromiseFields& b) { + return !(a == b); +} +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2PushPromiseFields& v); + +// Http2PingFields: + +struct Http2PingFields { + static constexpr size_t EncodedSize() { return 8; } + + uint8_t opaque_bytes[8]; +}; + +HTTP2_EXPORT_PRIVATE bool operator==(const Http2PingFields& a, + const Http2PingFields& b); +HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2PingFields& a, + const Http2PingFields& b) { + return !(a == b); +} +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2PingFields& v); + +// Http2GoAwayFields: + +struct Http2GoAwayFields { + Http2GoAwayFields() {} + Http2GoAwayFields(uint32_t last_stream_id, Http2ErrorCode error_code) + : last_stream_id(last_stream_id), error_code(error_code) {} + static constexpr size_t EncodedSize() { return 8; } + bool IsSupportedErrorCode() const { + return IsSupportedHttp2ErrorCode(error_code); + } + + uint32_t last_stream_id; + Http2ErrorCode error_code; +}; + +HTTP2_EXPORT_PRIVATE bool operator==(const Http2GoAwayFields& a, + const Http2GoAwayFields& b); +HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2GoAwayFields& a, + const Http2GoAwayFields& b) { + return !(a == b); +} +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2GoAwayFields& v); + +// Http2WindowUpdateFields: + +struct Http2WindowUpdateFields { + static constexpr size_t EncodedSize() { return 4; } + + // 31-bit, unsigned increase in the window size (only positive values are + // allowed). The high-bit is reserved for the future. + uint32_t window_size_increment; +}; + +HTTP2_EXPORT_PRIVATE bool operator==(const Http2WindowUpdateFields& a, + const Http2WindowUpdateFields& b); +HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2WindowUpdateFields& a, + const Http2WindowUpdateFields& b) { + return !(a == b); +} +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2WindowUpdateFields& v); + +// Http2AltSvcFields: + +struct Http2AltSvcFields { + static constexpr size_t EncodedSize() { return 2; } + + // This is the one fixed size portion of the ALTSVC payload. + uint16_t origin_length; +}; + +HTTP2_EXPORT_PRIVATE bool operator==(const Http2AltSvcFields& a, + const Http2AltSvcFields& b); +HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2AltSvcFields& a, + const Http2AltSvcFields& b) { + return !(a == b); +} +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2AltSvcFields& v); + +} // namespace http2 + +#endif // QUICHE_HTTP2_HTTP2_STRUCTURES_H_
diff --git a/http2/http2_structures_test.cc b/http2/http2_structures_test.cc new file mode 100644 index 0000000..30ebb36 --- /dev/null +++ b/http2/http2_structures_test.cc
@@ -0,0 +1,537 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/http2_structures.h" + +// Tests are focused on Http2FrameHeader because it has by far the most +// methods of any of the structures. +// Note that EXPECT.*DEATH tests are slow (a fork is probably involved). + +// And in case you're wondering, yes, these are ridiculously thorough tests, +// but believe it or not, I've found stupid bugs this way. + +#include <memory> +#include <ostream> +#include <sstream> +#include <tuple> +#include <type_traits> +#include <vector> + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/http2_structures_test_util.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" + +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using ::testing::Combine; +using ::testing::HasSubstr; +using ::testing::MatchesRegex; +using ::testing::Not; +using ::testing::Values; +using ::testing::ValuesIn; + +namespace http2 { +namespace test { +namespace { + +template <typename E> +E IncrementEnum(E e) { + using I = typename std::underlying_type<E>::type; + return static_cast<E>(1 + static_cast<I>(e)); +} + +template <class T> +AssertionResult VerifyRandomCalls() { + T t1; + Http2Random seq1; + Randomize(&t1, &seq1); + + T t2; + Http2Random seq2(seq1.Key()); + Randomize(&t2, &seq2); + + // The two Randomize calls should have made the same number of calls into + // the Http2Random implementations. + VERIFY_EQ(seq1.Rand64(), seq2.Rand64()); + + // And because Http2Random implementation is returning the same sequence, and + // Randomize should have been consistent in applying those results, the two + // Ts should have the same value. + VERIFY_EQ(t1, t2); + + Randomize(&t2, &seq2); + VERIFY_NE(t1, t2); + + Randomize(&t1, &seq1); + VERIFY_EQ(t1, t2); + + VERIFY_EQ(seq1.Rand64(), seq2.Rand64()); + + return AssertionSuccess(); +} + +#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG) +std::vector<Http2FrameType> ValidFrameTypes() { + std::vector<Http2FrameType> valid_types{Http2FrameType::DATA}; + while (valid_types.back() != Http2FrameType::ALTSVC) { + valid_types.push_back(IncrementEnum(valid_types.back())); + } + return valid_types; +} +#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG) + +TEST(Http2FrameHeaderTest, Constructor) { + Http2Random random; + uint8_t frame_type = 0; + do { + // Only the payload length is DCHECK'd in the constructor, so we need to + // make sure it is a "uint24". + uint32_t payload_length = random.Rand32() & 0xffffff; + Http2FrameType type = static_cast<Http2FrameType>(frame_type); + uint8_t flags = random.Rand8(); + uint32_t stream_id = random.Rand32(); + + Http2FrameHeader v(payload_length, type, flags, stream_id); + + EXPECT_EQ(payload_length, v.payload_length); + EXPECT_EQ(type, v.type); + EXPECT_EQ(flags, v.flags); + EXPECT_EQ(stream_id, v.stream_id); + } while (frame_type++ == 255); + +#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG) + EXPECT_DEBUG_DEATH(Http2FrameHeader(0x01000000, Http2FrameType::DATA, 0, 1), + "payload_length"); +#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG) +} + +TEST(Http2FrameHeaderTest, Eq) { + Http2Random random; + uint32_t payload_length = random.Rand32() & 0xffffff; + Http2FrameType type = static_cast<Http2FrameType>(random.Rand8()); + + uint8_t flags = random.Rand8(); + uint32_t stream_id = random.Rand32(); + + Http2FrameHeader v(payload_length, type, flags, stream_id); + + EXPECT_EQ(payload_length, v.payload_length); + EXPECT_EQ(type, v.type); + EXPECT_EQ(flags, v.flags); + EXPECT_EQ(stream_id, v.stream_id); + + Http2FrameHeader u(0, type, ~flags, stream_id); + + EXPECT_NE(u, v); + EXPECT_NE(v, u); + EXPECT_FALSE(u == v); + EXPECT_FALSE(v == u); + EXPECT_TRUE(u != v); + EXPECT_TRUE(v != u); + + u = v; + + EXPECT_EQ(u, v); + EXPECT_EQ(v, u); + EXPECT_TRUE(u == v); + EXPECT_TRUE(v == u); + EXPECT_FALSE(u != v); + EXPECT_FALSE(v != u); + + EXPECT_TRUE(VerifyRandomCalls<Http2FrameHeader>()); +} + +#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG) +// The tests of the valid frame types include EXPECT_DEBUG_DEATH, which is +// quite slow, so using value parameterized tests in order to allow sharding. +class Http2FrameHeaderTypeAndFlagTest + : public ::testing::TestWithParam< + std::tuple<Http2FrameType, Http2FrameFlag>> { + protected: + Http2FrameHeaderTypeAndFlagTest() + : type_(std::get<0>(GetParam())), flags_(std::get<1>(GetParam())) { + LOG(INFO) << "Frame type: " << type_; + LOG(INFO) << "Frame flags: " << Http2FrameFlagsToString(type_, flags_); + } + + const Http2FrameType type_; + const Http2FrameFlag flags_; +}; + +class IsEndStreamTest : public Http2FrameHeaderTypeAndFlagTest {}; +INSTANTIATE_TEST_CASE_P(IsEndStream, + IsEndStreamTest, + Combine(ValuesIn(ValidFrameTypes()), + Values(~Http2FrameFlag::END_STREAM, 0xff))); +TEST_P(IsEndStreamTest, IsEndStream) { + const bool is_set = + (flags_ & Http2FrameFlag::END_STREAM) == Http2FrameFlag::END_STREAM; + Http2String flags_string; + Http2FrameHeader v(0, type_, flags_, 0); + switch (type_) { + case Http2FrameType::DATA: + case Http2FrameType::HEADERS: + EXPECT_EQ(is_set, v.IsEndStream()) << v; + flags_string = v.FlagsToString(); + if (is_set) { + EXPECT_THAT(flags_string, MatchesRegex(".*\\|?END_STREAM\\|.*")); + } else { + EXPECT_THAT(flags_string, Not(HasSubstr("END_STREAM"))); + } + v.RetainFlags(Http2FrameFlag::END_STREAM); + EXPECT_EQ(is_set, v.IsEndStream()) << v; + { + std::stringstream s; + s << v; + EXPECT_EQ(v.ToString(), s.str()); + if (is_set) { + EXPECT_THAT(s.str(), HasSubstr("flags=END_STREAM,")); + } else { + EXPECT_THAT(s.str(), HasSubstr("flags=,")); + } + } + break; + default: + EXPECT_DEBUG_DEATH(v.IsEndStream(), "DATA.*HEADERS") << v; + } +} + +class IsACKTest : public Http2FrameHeaderTypeAndFlagTest {}; +INSTANTIATE_TEST_CASE_P(IsAck, + IsACKTest, + Combine(ValuesIn(ValidFrameTypes()), + Values(~Http2FrameFlag::ACK, 0xff))); +TEST_P(IsACKTest, IsAck) { + const bool is_set = (flags_ & Http2FrameFlag::ACK) == Http2FrameFlag::ACK; + Http2String flags_string; + Http2FrameHeader v(0, type_, flags_, 0); + switch (type_) { + case Http2FrameType::SETTINGS: + case Http2FrameType::PING: + EXPECT_EQ(is_set, v.IsAck()) << v; + flags_string = v.FlagsToString(); + if (is_set) { + EXPECT_THAT(flags_string, MatchesRegex(".*\\|?ACK\\|.*")); + } else { + EXPECT_THAT(flags_string, Not(HasSubstr("ACK"))); + } + v.RetainFlags(Http2FrameFlag::ACK); + EXPECT_EQ(is_set, v.IsAck()) << v; + { + std::stringstream s; + s << v; + EXPECT_EQ(v.ToString(), s.str()); + if (is_set) { + EXPECT_THAT(s.str(), HasSubstr("flags=ACK,")); + } else { + EXPECT_THAT(s.str(), HasSubstr("flags=,")); + } + } + break; + default: + EXPECT_DEBUG_DEATH(v.IsAck(), "SETTINGS.*PING") << v; + } +} + +class IsEndHeadersTest : public Http2FrameHeaderTypeAndFlagTest {}; +INSTANTIATE_TEST_CASE_P(IsEndHeaders, + IsEndHeadersTest, + Combine(ValuesIn(ValidFrameTypes()), + Values(~Http2FrameFlag::END_HEADERS, 0xff))); +TEST_P(IsEndHeadersTest, IsEndHeaders) { + const bool is_set = + (flags_ & Http2FrameFlag::END_HEADERS) == Http2FrameFlag::END_HEADERS; + Http2String flags_string; + Http2FrameHeader v(0, type_, flags_, 0); + switch (type_) { + case Http2FrameType::HEADERS: + case Http2FrameType::PUSH_PROMISE: + case Http2FrameType::CONTINUATION: + EXPECT_EQ(is_set, v.IsEndHeaders()) << v; + flags_string = v.FlagsToString(); + if (is_set) { + EXPECT_THAT(flags_string, MatchesRegex(".*\\|?END_HEADERS\\|.*")); + } else { + EXPECT_THAT(flags_string, Not(HasSubstr("END_HEADERS"))); + } + v.RetainFlags(Http2FrameFlag::END_HEADERS); + EXPECT_EQ(is_set, v.IsEndHeaders()) << v; + { + std::stringstream s; + s << v; + EXPECT_EQ(v.ToString(), s.str()); + if (is_set) { + EXPECT_THAT(s.str(), HasSubstr("flags=END_HEADERS,")); + } else { + EXPECT_THAT(s.str(), HasSubstr("flags=,")); + } + } + break; + default: + EXPECT_DEBUG_DEATH(v.IsEndHeaders(), + "HEADERS.*PUSH_PROMISE.*CONTINUATION") + << v; + } +} + +class IsPaddedTest : public Http2FrameHeaderTypeAndFlagTest {}; +INSTANTIATE_TEST_CASE_P(IsPadded, + IsPaddedTest, + Combine(ValuesIn(ValidFrameTypes()), + Values(~Http2FrameFlag::PADDED, 0xff))); +TEST_P(IsPaddedTest, IsPadded) { + const bool is_set = + (flags_ & Http2FrameFlag::PADDED) == Http2FrameFlag::PADDED; + Http2String flags_string; + Http2FrameHeader v(0, type_, flags_, 0); + switch (type_) { + case Http2FrameType::DATA: + case Http2FrameType::HEADERS: + case Http2FrameType::PUSH_PROMISE: + EXPECT_EQ(is_set, v.IsPadded()) << v; + flags_string = v.FlagsToString(); + if (is_set) { + EXPECT_THAT(flags_string, MatchesRegex(".*\\|?PADDED\\|.*")); + } else { + EXPECT_THAT(flags_string, Not(HasSubstr("PADDED"))); + } + v.RetainFlags(Http2FrameFlag::PADDED); + EXPECT_EQ(is_set, v.IsPadded()) << v; + { + std::stringstream s; + s << v; + EXPECT_EQ(v.ToString(), s.str()); + if (is_set) { + EXPECT_THAT(s.str(), HasSubstr("flags=PADDED,")); + } else { + EXPECT_THAT(s.str(), HasSubstr("flags=,")); + } + } + break; + default: + EXPECT_DEBUG_DEATH(v.IsPadded(), "DATA.*HEADERS.*PUSH_PROMISE") << v; + } +} + +class HasPriorityTest : public Http2FrameHeaderTypeAndFlagTest {}; +INSTANTIATE_TEST_CASE_P(HasPriority, + HasPriorityTest, + Combine(ValuesIn(ValidFrameTypes()), + Values(~Http2FrameFlag::PRIORITY, 0xff))); +TEST_P(HasPriorityTest, HasPriority) { + const bool is_set = + (flags_ & Http2FrameFlag::PRIORITY) == Http2FrameFlag::PRIORITY; + Http2String flags_string; + Http2FrameHeader v(0, type_, flags_, 0); + switch (type_) { + case Http2FrameType::HEADERS: + EXPECT_EQ(is_set, v.HasPriority()) << v; + flags_string = v.FlagsToString(); + if (is_set) { + EXPECT_THAT(flags_string, MatchesRegex(".*\\|?PRIORITY\\|.*")); + } else { + EXPECT_THAT(flags_string, Not(HasSubstr("PRIORITY"))); + } + v.RetainFlags(Http2FrameFlag::PRIORITY); + EXPECT_EQ(is_set, v.HasPriority()) << v; + { + std::stringstream s; + s << v; + EXPECT_EQ(v.ToString(), s.str()); + if (is_set) { + EXPECT_THAT(s.str(), HasSubstr("flags=PRIORITY,")); + } else { + EXPECT_THAT(s.str(), HasSubstr("flags=,")); + } + } + break; + default: + EXPECT_DEBUG_DEATH(v.HasPriority(), "HEADERS") << v; + } +} + +TEST(Http2PriorityFieldsTest, Constructor) { + Http2Random random; + uint32_t stream_dependency = random.Rand32() & StreamIdMask(); + uint32_t weight = 1 + random.Rand8(); + bool is_exclusive = random.OneIn(2); + + Http2PriorityFields v(stream_dependency, weight, is_exclusive); + + EXPECT_EQ(stream_dependency, v.stream_dependency); + EXPECT_EQ(weight, v.weight); + EXPECT_EQ(is_exclusive, v.is_exclusive); + + // The high-bit must not be set on the stream id. + EXPECT_DEBUG_DEATH( + Http2PriorityFields(stream_dependency | 0x80000000, weight, is_exclusive), + "31-bit"); + + // The weight must be in the range 1-256. + EXPECT_DEBUG_DEATH(Http2PriorityFields(stream_dependency, 0, is_exclusive), + "too small"); + EXPECT_DEBUG_DEATH( + Http2PriorityFields(stream_dependency, weight + 256, is_exclusive), + "too large"); + + EXPECT_TRUE(VerifyRandomCalls<Http2PriorityFields>()); +} +#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG) + +TEST(Http2RstStreamFieldsTest, IsSupported) { + Http2RstStreamFields v{Http2ErrorCode::HTTP2_NO_ERROR}; + EXPECT_TRUE(v.IsSupportedErrorCode()) << v; + + Http2RstStreamFields u{static_cast<Http2ErrorCode>(~0)}; + EXPECT_FALSE(u.IsSupportedErrorCode()) << v; + + EXPECT_TRUE(VerifyRandomCalls<Http2RstStreamFields>()); +} + +TEST(Http2SettingFieldsTest, Misc) { + Http2Random random; + Http2SettingsParameter parameter = + static_cast<Http2SettingsParameter>(random.Rand16()); + uint32_t value = random.Rand32(); + + Http2SettingFields v(parameter, value); + + EXPECT_EQ(v, v); + EXPECT_EQ(parameter, v.parameter); + EXPECT_EQ(value, v.value); + + if (static_cast<uint16_t>(parameter) < 7) { + EXPECT_TRUE(v.IsSupportedParameter()) << v; + } else { + EXPECT_FALSE(v.IsSupportedParameter()) << v; + } + + Http2SettingFields u(parameter, ~value); + EXPECT_NE(v, u); + EXPECT_EQ(v.parameter, u.parameter); + EXPECT_NE(v.value, u.value); + + Http2SettingFields w(IncrementEnum(parameter), value); + EXPECT_NE(v, w); + EXPECT_NE(v.parameter, w.parameter); + EXPECT_EQ(v.value, w.value); + + Http2SettingFields x(Http2SettingsParameter::MAX_FRAME_SIZE, 123); + std::stringstream s; + s << x; + EXPECT_EQ("parameter=MAX_FRAME_SIZE, value=123", s.str()); + + EXPECT_TRUE(VerifyRandomCalls<Http2SettingFields>()); +} + +TEST(Http2PushPromiseTest, Misc) { + Http2Random random; + uint32_t promised_stream_id = random.Rand32() & StreamIdMask(); + + Http2PushPromiseFields v{promised_stream_id}; + EXPECT_EQ(promised_stream_id, v.promised_stream_id); + EXPECT_EQ(v, v); + + std::stringstream s; + s << v; + EXPECT_EQ(Http2StrCat("promised_stream_id=", promised_stream_id), s.str()); + + // High-bit is reserved, but not used, so we can set it. + promised_stream_id |= 0x80000000; + Http2PushPromiseFields w{promised_stream_id}; + EXPECT_EQ(w, w); + EXPECT_NE(v, w); + + v.promised_stream_id = promised_stream_id; + EXPECT_EQ(v, w); + + EXPECT_TRUE(VerifyRandomCalls<Http2PushPromiseFields>()); +} + +TEST(Http2PingFieldsTest, Misc) { + Http2PingFields v{{'8', ' ', 'b', 'y', 't', 'e', 's', '\0'}}; + std::stringstream s; + s << v; + EXPECT_EQ("opaque_bytes=0x3820627974657300", s.str()); + + EXPECT_TRUE(VerifyRandomCalls<Http2PingFields>()); +} + +TEST(Http2GoAwayFieldsTest, Misc) { + Http2Random random; + uint32_t last_stream_id = random.Rand32() & StreamIdMask(); + Http2ErrorCode error_code = static_cast<Http2ErrorCode>(random.Rand32()); + + Http2GoAwayFields v(last_stream_id, error_code); + EXPECT_EQ(v, v); + EXPECT_EQ(last_stream_id, v.last_stream_id); + EXPECT_EQ(error_code, v.error_code); + + if (static_cast<uint32_t>(error_code) < 14) { + EXPECT_TRUE(v.IsSupportedErrorCode()) << v; + } else { + EXPECT_FALSE(v.IsSupportedErrorCode()) << v; + } + + Http2GoAwayFields u(~last_stream_id, error_code); + EXPECT_NE(v, u); + EXPECT_NE(v.last_stream_id, u.last_stream_id); + EXPECT_EQ(v.error_code, u.error_code); + + EXPECT_TRUE(VerifyRandomCalls<Http2GoAwayFields>()); +} + +TEST(Http2WindowUpdateTest, Misc) { + Http2Random random; + uint32_t window_size_increment = random.Rand32() & UInt31Mask(); + + Http2WindowUpdateFields v{window_size_increment}; + EXPECT_EQ(window_size_increment, v.window_size_increment); + EXPECT_EQ(v, v); + + std::stringstream s; + s << v; + EXPECT_EQ(Http2StrCat("window_size_increment=", window_size_increment), + s.str()); + + // High-bit is reserved, but not used, so we can set it. + window_size_increment |= 0x80000000; + Http2WindowUpdateFields w{window_size_increment}; + EXPECT_EQ(w, w); + EXPECT_NE(v, w); + + v.window_size_increment = window_size_increment; + EXPECT_EQ(v, w); + + EXPECT_TRUE(VerifyRandomCalls<Http2WindowUpdateFields>()); +} + +TEST(Http2AltSvcTest, Misc) { + Http2Random random; + uint16_t origin_length = random.Rand16(); + + Http2AltSvcFields v{origin_length}; + EXPECT_EQ(origin_length, v.origin_length); + EXPECT_EQ(v, v); + + std::stringstream s; + s << v; + EXPECT_EQ(Http2StrCat("origin_length=", origin_length), s.str()); + + Http2AltSvcFields w{++origin_length}; + EXPECT_EQ(w, w); + EXPECT_NE(v, w); + + v.origin_length = w.origin_length; + EXPECT_EQ(v, w); + + EXPECT_TRUE(VerifyRandomCalls<Http2AltSvcFields>()); +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/http2_structures_test_util.cc b/http2/http2_structures_test_util.cc new file mode 100644 index 0000000..4a1a74d --- /dev/null +++ b/http2/http2_structures_test_util.cc
@@ -0,0 +1,109 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/http2_structures_test_util.h" + +#include <cstdint> + +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_constants_test_util.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" + +namespace http2 { +namespace test { + +void Randomize(Http2FrameHeader* p, Http2Random* rng) { + p->payload_length = rng->Rand32() & 0xffffff; + p->type = static_cast<Http2FrameType>(rng->Rand8()); + p->flags = static_cast<Http2FrameFlag>(rng->Rand8()); + p->stream_id = rng->Rand32() & StreamIdMask(); +} +void Randomize(Http2PriorityFields* p, Http2Random* rng) { + p->stream_dependency = rng->Rand32() & StreamIdMask(); + p->weight = rng->Rand8() + 1; + p->is_exclusive = rng->OneIn(2); +} +void Randomize(Http2RstStreamFields* p, Http2Random* rng) { + p->error_code = static_cast<Http2ErrorCode>(rng->Rand32()); +} +void Randomize(Http2SettingFields* p, Http2Random* rng) { + p->parameter = static_cast<Http2SettingsParameter>(rng->Rand16()); + p->value = rng->Rand32(); +} +void Randomize(Http2PushPromiseFields* p, Http2Random* rng) { + p->promised_stream_id = rng->Rand32() & StreamIdMask(); +} +void Randomize(Http2PingFields* p, Http2Random* rng) { + for (int ndx = 0; ndx < 8; ++ndx) { + p->opaque_bytes[ndx] = rng->Rand8(); + } +} +void Randomize(Http2GoAwayFields* p, Http2Random* rng) { + p->last_stream_id = rng->Rand32() & StreamIdMask(); + p->error_code = static_cast<Http2ErrorCode>(rng->Rand32()); +} +void Randomize(Http2WindowUpdateFields* p, Http2Random* rng) { + p->window_size_increment = rng->Rand32() & 0x7fffffff; +} +void Randomize(Http2AltSvcFields* p, Http2Random* rng) { + p->origin_length = rng->Rand16(); +} + +void ScrubFlagsOfHeader(Http2FrameHeader* header) { + uint8_t invalid_mask = InvalidFlagMaskForFrameType(header->type); + uint8_t keep_mask = ~invalid_mask; + header->RetainFlags(keep_mask); +} + +bool FrameIsPadded(const Http2FrameHeader& header) { + switch (header.type) { + case Http2FrameType::DATA: + case Http2FrameType::HEADERS: + case Http2FrameType::PUSH_PROMISE: + return header.IsPadded(); + default: + return false; + } +} + +bool FrameHasPriority(const Http2FrameHeader& header) { + switch (header.type) { + case Http2FrameType::HEADERS: + return header.HasPriority(); + case Http2FrameType::PRIORITY: + return true; + default: + return false; + } +} + +bool FrameCanHavePayload(const Http2FrameHeader& header) { + switch (header.type) { + case Http2FrameType::DATA: + case Http2FrameType::HEADERS: + case Http2FrameType::PUSH_PROMISE: + case Http2FrameType::CONTINUATION: + case Http2FrameType::PING: + case Http2FrameType::GOAWAY: + case Http2FrameType::ALTSVC: + return true; + default: + return false; + } +} + +bool FrameCanHaveHpackPayload(const Http2FrameHeader& header) { + switch (header.type) { + case Http2FrameType::HEADERS: + case Http2FrameType::PUSH_PROMISE: + case Http2FrameType::CONTINUATION: + return true; + default: + return false; + } +} + +} // namespace test +} // namespace http2
diff --git a/http2/http2_structures_test_util.h b/http2/http2_structures_test_util.h new file mode 100644 index 0000000..86fbf3f --- /dev/null +++ b/http2/http2_structures_test_util.h
@@ -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. + +#ifndef QUICHE_HTTP2_HTTP2_STRUCTURES_TEST_UTIL_H_ +#define QUICHE_HTTP2_HTTP2_STRUCTURES_TEST_UTIL_H_ + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h" + +namespace http2 { +namespace test { + +template <class S> +Http2String SerializeStructure(const S& s) { + Http2FrameBuilder fb; + fb.Append(s); + EXPECT_EQ(S::EncodedSize(), fb.size()); + return fb.buffer(); +} + +// Randomize the members of out, in a manner that yields encodeable contents +// (e.g. a "uint24" field has only the low 24 bits set). +void Randomize(Http2FrameHeader* out, Http2Random* rng); +void Randomize(Http2PriorityFields* out, Http2Random* rng); +void Randomize(Http2RstStreamFields* out, Http2Random* rng); +void Randomize(Http2SettingFields* out, Http2Random* rng); +void Randomize(Http2PushPromiseFields* out, Http2Random* rng); +void Randomize(Http2PingFields* out, Http2Random* rng); +void Randomize(Http2GoAwayFields* out, Http2Random* rng); +void Randomize(Http2WindowUpdateFields* out, Http2Random* rng); +void Randomize(Http2AltSvcFields* out, Http2Random* rng); + +// Clear bits of header->flags that are known to be invalid for the +// type. For unknown frame types, no change is made. +void ScrubFlagsOfHeader(Http2FrameHeader* header); + +// Is the frame with this header padded? Only true for known/supported frame +// types. +bool FrameIsPadded(const Http2FrameHeader& header); + +// Does the frame with this header have Http2PriorityFields? +bool FrameHasPriority(const Http2FrameHeader& header); + +// Does the frame with this header have a variable length payload (including +// empty) payload (e.g. DATA or HEADERS)? Really a test of the frame type. +bool FrameCanHavePayload(const Http2FrameHeader& header); + +// Does the frame with this header have a variable length HPACK payload +// (including empty) payload (e.g. HEADERS)? Really a test of the frame type. +bool FrameCanHaveHpackPayload(const Http2FrameHeader& header); + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_HTTP2_STRUCTURES_TEST_UTIL_H_
diff --git a/http2/platform/api/http2_arraysize.h b/http2/platform/api/http2_arraysize.h new file mode 100644 index 0000000..417e53b --- /dev/null +++ b/http2/platform/api/http2_arraysize.h
@@ -0,0 +1,12 @@ +// 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. + +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_ARRAYSIZE_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_ARRAYSIZE_H_ + +#include "net/http2/platform/impl/http2_arraysize_impl.h" + +#define HTTP2_ARRAYSIZE(x) HTTP2_ARRAYSIZE_IMPL(x) + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_ARRAYSIZE_H_
diff --git a/http2/platform/api/http2_bug_tracker.h b/http2/platform/api/http2_bug_tracker.h new file mode 100644 index 0000000..93ad677 --- /dev/null +++ b/http2/platform/api/http2_bug_tracker.h
@@ -0,0 +1,15 @@ +// 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_PLATFORM_API_HTTP2_BUG_TRACKER_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_BUG_TRACKER_H_ + +#include "net/http2/platform/impl/http2_bug_tracker_impl.h" + +#define HTTP2_BUG HTTP2_BUG_IMPL +#define HTTP2_BUG_IF HTTP2_BUG_IF_IMPL +#define FLAGS_http2_always_log_bugs_for_tests \ + FLAGS_http2_always_log_bugs_for_tests_IMPL + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_BUG_TRACKER_H_
diff --git a/http2/platform/api/http2_containers.h b/http2/platform/api/http2_containers.h new file mode 100644 index 0000000..e748e7b --- /dev/null +++ b/http2/platform/api/http2_containers.h
@@ -0,0 +1,17 @@ +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_CONTAINERS_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_CONTAINERS_H_ + +#include "net/http2/platform/impl/http2_containers_impl.h" + +namespace http2 { + +// Represents a double-ended queue which may be backed by a list or a flat +// circular buffer. +// +// DOES NOT GUARANTEE POINTER OR ITERATOR STABILITY! +template <typename T> +using Http2Deque = Http2DequeImpl<T>; + +} // namespace http2 + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_CONTAINERS_H_
diff --git a/http2/platform/api/http2_estimate_memory_usage.h b/http2/platform/api/http2_estimate_memory_usage.h new file mode 100644 index 0000000..fd405fa --- /dev/null +++ b/http2/platform/api/http2_estimate_memory_usage.h
@@ -0,0 +1,21 @@ +// 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_PLATFORM_API_HTTP2_ESTIMATE_MEMORY_USAGE_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_ESTIMATE_MEMORY_USAGE_H_ + +#include <cstddef> + +#include "net/http2/platform/impl/http2_estimate_memory_usage_impl.h" + +namespace http2 { + +template <class T> +size_t Http2EstimateMemoryUsage(const T& object) { + return Http2EstimateMemoryUsageImpl(object); +} + +} // namespace http2 + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_ESTIMATE_MEMORY_USAGE_H_
diff --git a/http2/platform/api/http2_export.h b/http2/platform/api/http2_export.h new file mode 100644 index 0000000..e262f74 --- /dev/null +++ b/http2/platform/api/http2_export.h
@@ -0,0 +1,10 @@ +// 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_PLATFORM_API_HTTP2_EXPORT_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_EXPORT_H_ + +#include "net/http2/platform/impl/http2_export_impl.h" + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_EXPORT_H_
diff --git a/http2/platform/api/http2_flag_utils.h b/http2/platform/api/http2_flag_utils.h new file mode 100644 index 0000000..4303c43 --- /dev/null +++ b/http2/platform/api/http2_flag_utils.h
@@ -0,0 +1,12 @@ +// 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. + +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_FLAG_UTILS_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_FLAG_UTILS_H_ + +#include "net/http2/platform/impl/http2_flag_utils_impl.h" + +#define HTTP2_RELOADABLE_FLAG_COUNT HTTP2_RELOADABLE_FLAG_COUNT_IMPL + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_FLAG_UTILS_H_
diff --git a/http2/platform/api/http2_flags.h b/http2/platform/api/http2_flags.h new file mode 100644 index 0000000..08f95da --- /dev/null +++ b/http2/platform/api/http2_flags.h
@@ -0,0 +1,14 @@ +// 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. + +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_FLAGS_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_FLAGS_H_ + +#include "net/http2/platform/impl/http2_flags_impl.h" + +#define GetHttp2ReloadableFlag(flag) GetHttp2ReloadableFlagImpl(flag) +#define SetHttp2ReloadableFlag(flag, value) \ + SetHttp2ReloadableFlagImpl(flag, value) + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_FLAGS_H_
diff --git a/http2/platform/api/http2_macros.h b/http2/platform/api/http2_macros.h new file mode 100644 index 0000000..0be5e89 --- /dev/null +++ b/http2/platform/api/http2_macros.h
@@ -0,0 +1,10 @@ +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_MACROS_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_MACROS_H_ + +#include "net/http2/platform/impl/http2_macros_impl.h" + +#define HTTP2_FALLTHROUGH HTTP2_FALLTHROUGH_IMPL +#define HTTP2_UNREACHABLE() HTTP2_UNREACHABLE_IMPL() +#define HTTP2_DIE_IF_NULL(ptr) HTTP2_DIE_IF_NULL_IMPL(ptr) + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_MACROS_H_
diff --git a/http2/platform/api/http2_mock_log.h b/http2/platform/api/http2_mock_log.h new file mode 100644 index 0000000..3d16837 --- /dev/null +++ b/http2/platform/api/http2_mock_log.h
@@ -0,0 +1,18 @@ +// 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. + +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_MOCK_LOG_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_MOCK_LOG_H_ + +#include "net/http2/platform/impl/http2_mock_log_impl.h" + +using Http2MockLog = Http2MockLogImpl; +#define CREATE_HTTP2_MOCK_LOG(log) CREATE_HTTP2_MOCK_LOG_IMPL(log) + +#define EXPECT_HTTP2_LOG_CALL(log) EXPECT_HTTP2_LOG_CALL_IMPL(log) + +#define EXPECT_HTTP2_LOG_CALL_CONTAINS(log, level, content) \ + EXPECT_HTTP2_LOG_CALL_CONTAINS_IMPL(log, level, content) + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_MOCK_LOG_H_
diff --git a/http2/platform/api/http2_optional.h b/http2/platform/api/http2_optional.h new file mode 100644 index 0000000..64ce5f5 --- /dev/null +++ b/http2/platform/api/http2_optional.h
@@ -0,0 +1,19 @@ +// 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. + +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_OPTIONAL_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_OPTIONAL_H_ + +#include <utility> + +#include "net/http2/platform/impl/http2_optional_impl.h" + +namespace http2 { + +template <typename T> +using Http2Optional = Http2OptionalImpl<T>; + +} // namespace http2 + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_OPTIONAL_H_
diff --git a/http2/platform/api/http2_ptr_util.h b/http2/platform/api/http2_ptr_util.h new file mode 100644 index 0000000..2530e7c --- /dev/null +++ b/http2/platform/api/http2_ptr_util.h
@@ -0,0 +1,22 @@ +// 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. + +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_PTR_UTIL_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_PTR_UTIL_H_ + +#include <memory> +#include <utility> + +#include "net/http2/platform/impl/http2_ptr_util_impl.h" + +namespace http2 { + +template <typename T, typename... Args> +std::unique_ptr<T> Http2MakeUnique(Args&&... args) { + return Http2MakeUniqueImpl<T>(std::forward<Args>(args)...); +} + +} // namespace http2 + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_PTR_UTIL_H_
diff --git a/http2/platform/api/http2_reconstruct_object.h b/http2/platform/api/http2_reconstruct_object.h new file mode 100644 index 0000000..ddcb312 --- /dev/null +++ b/http2/platform/api/http2_reconstruct_object.h
@@ -0,0 +1,34 @@ +// 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_PLATFORM_API_HTTP2_RECONSTRUCT_OBJECT_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_RECONSTRUCT_OBJECT_H_ + +#include <utility> + +#include "net/http2/platform/impl/http2_reconstruct_object_impl.h" + +namespace http2 { +namespace test { + +class Http2Random; + +// Reconstruct an object so that it is initialized as when it was first +// constructed. Runs the destructor to handle objects that might own resources, +// and runs the constructor with the provided arguments, if any. +template <class T, class... Args> +void Http2ReconstructObject(T* ptr, Http2Random* rng, Args&&... args) { + Http2ReconstructObjectImpl(ptr, rng, std::forward<Args>(args)...); +} + +// This version applies default-initialization to the object. +template <class T> +void Http2DefaultReconstructObject(T* ptr, Http2Random* rng) { + Http2DefaultReconstructObjectImpl(ptr, rng); +} + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_RECONSTRUCT_OBJECT_H_
diff --git a/http2/platform/api/http2_string.h b/http2/platform/api/http2_string.h new file mode 100644 index 0000000..25f9e59 --- /dev/null +++ b/http2/platform/api/http2_string.h
@@ -0,0 +1,16 @@ +// Copyright (c) 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_PLATFORM_API_HTTP2_STRING_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_H_ + +#include "net/http2/platform/impl/http2_string_impl.h" + +namespace http2 { + +using Http2String = Http2StringImpl; + +} // namespace http2 + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_H_
diff --git a/http2/platform/api/http2_string_piece.h b/http2/platform/api/http2_string_piece.h new file mode 100644 index 0000000..92fb3ef --- /dev/null +++ b/http2/platform/api/http2_string_piece.h
@@ -0,0 +1,16 @@ +// Copyright (c) 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_PLATFORM_API_HTTP2_STRING_PIECE_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_PIECE_H_ + +#include "net/http2/platform/impl/http2_string_piece_impl.h" + +namespace http2 { + +using Http2StringPiece = Http2StringPieceImpl; + +} // namespace http2 + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_PIECE_H_
diff --git a/http2/platform/api/http2_string_utils.h b/http2/platform/api/http2_string_utils.h new file mode 100644 index 0000000..ba40560 --- /dev/null +++ b/http2/platform/api/http2_string_utils.h
@@ -0,0 +1,56 @@ +// 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_PLATFORM_API_HTTP2_STRING_UTILS_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_UTILS_H_ + +#include <type_traits> +#include <utility> + +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/http2/platform/impl/http2_string_utils_impl.h" + +namespace http2 { + +template <typename... Args> +inline Http2String Http2StrCat(const Args&... args) { + return Http2StrCatImpl(std::forward<const Args&>(args)...); +} + +template <typename... Args> +inline void Http2StrAppend(Http2String* output, const Args&... args) { + Http2StrAppendImpl(output, std::forward<const Args&>(args)...); +} + +template <typename... Args> +inline Http2String Http2StringPrintf(const Args&... args) { + return Http2StringPrintfImpl(std::forward<const Args&>(args)...); +} + +inline Http2String Http2HexEncode(const void* bytes, size_t size) { + return Http2HexEncodeImpl(bytes, size); +} + +inline Http2String Http2HexDecode(Http2StringPiece data) { + return Http2HexDecodeImpl(data); +} + +inline Http2String Http2HexDump(Http2StringPiece data) { + return Http2HexDumpImpl(data); +} + +inline Http2String Http2HexEscape(Http2StringPiece data) { + return Http2HexEscapeImpl(data); +} + +template <typename Number> +inline Http2String Http2Hex(Number number) { + static_assert(std::is_integral<Number>::value, "Number has to be an int"); + return Http2HexImpl(number); +} + +} // namespace http2 + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_UTILS_H_
diff --git a/http2/platform/api/http2_string_utils_test.cc b/http2/platform/api/http2_string_utils_test.cc new file mode 100644 index 0000000..254f9d7 --- /dev/null +++ b/http2/platform/api/http2_string_utils_test.cc
@@ -0,0 +1,178 @@ +// 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 "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +#include <cstdint> + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { +namespace test { +namespace { + +TEST(Http2StringUtilsTest, Http2StrCat) { + // No arguments. + EXPECT_EQ("", Http2StrCat()); + + // Single string-like argument. + const char kFoo[] = "foo"; + const Http2String string_foo(kFoo); + const Http2StringPiece stringpiece_foo(string_foo); + EXPECT_EQ("foo", Http2StrCat(kFoo)); + EXPECT_EQ("foo", Http2StrCat(string_foo)); + EXPECT_EQ("foo", Http2StrCat(stringpiece_foo)); + + // Two string-like arguments. + const char kBar[] = "bar"; + const Http2StringPiece stringpiece_bar(kBar); + const Http2String string_bar(kBar); + EXPECT_EQ("foobar", Http2StrCat(kFoo, kBar)); + EXPECT_EQ("foobar", Http2StrCat(kFoo, string_bar)); + EXPECT_EQ("foobar", Http2StrCat(kFoo, stringpiece_bar)); + EXPECT_EQ("foobar", Http2StrCat(string_foo, kBar)); + EXPECT_EQ("foobar", Http2StrCat(string_foo, string_bar)); + EXPECT_EQ("foobar", Http2StrCat(string_foo, stringpiece_bar)); + EXPECT_EQ("foobar", Http2StrCat(stringpiece_foo, kBar)); + EXPECT_EQ("foobar", Http2StrCat(stringpiece_foo, string_bar)); + EXPECT_EQ("foobar", Http2StrCat(stringpiece_foo, stringpiece_bar)); + + // Many-many arguments. + EXPECT_EQ( + "foobarbazquxquuxquuzcorgegraultgarplywaldofredplughxyzzythud", + Http2StrCat("foo", "bar", "baz", "qux", "quux", "quuz", "corge", "grault", + "garply", "waldo", "fred", "plugh", "xyzzy", "thud")); + + // Numerical arguments. + const int16_t i = 1; + const uint64_t u = 8; + const double d = 3.1415; + + EXPECT_EQ("1 8", Http2StrCat(i, " ", u)); + EXPECT_EQ("3.14151181", Http2StrCat(d, i, i, u, i)); + EXPECT_EQ("i: 1, u: 8, d: 3.1415", + Http2StrCat("i: ", i, ", u: ", u, ", d: ", d)); + + // Boolean arguments. + const bool t = true; + const bool f = false; + + EXPECT_EQ("1", Http2StrCat(t)); + EXPECT_EQ("0", Http2StrCat(f)); + EXPECT_EQ("0110", Http2StrCat(f, t, t, f)); + + // Mixed string-like, numerical, and Boolean arguments. + EXPECT_EQ("foo1foo081bar3.14151", + Http2StrCat(kFoo, i, string_foo, f, u, t, stringpiece_bar, d, t)); + EXPECT_EQ("3.141511bar18bar13.14150", + Http2StrCat(d, t, t, string_bar, i, u, kBar, t, d, f)); +} + +TEST(Http2StringUtilsTest, Http2StrAppend) { + // No arguments on empty string. + Http2String output; + Http2StrAppend(&output); + EXPECT_TRUE(output.empty()); + + // Single string-like argument. + const char kFoo[] = "foo"; + const Http2String string_foo(kFoo); + const Http2StringPiece stringpiece_foo(string_foo); + Http2StrAppend(&output, kFoo); + EXPECT_EQ("foo", output); + Http2StrAppend(&output, string_foo); + EXPECT_EQ("foofoo", output); + Http2StrAppend(&output, stringpiece_foo); + EXPECT_EQ("foofoofoo", output); + + // No arguments on non-empty string. + Http2StrAppend(&output); + EXPECT_EQ("foofoofoo", output); + + output.clear(); + + // Two string-like arguments. + const char kBar[] = "bar"; + const Http2StringPiece stringpiece_bar(kBar); + const Http2String string_bar(kBar); + Http2StrAppend(&output, kFoo, kBar); + EXPECT_EQ("foobar", output); + Http2StrAppend(&output, kFoo, string_bar); + EXPECT_EQ("foobarfoobar", output); + Http2StrAppend(&output, kFoo, stringpiece_bar); + EXPECT_EQ("foobarfoobarfoobar", output); + Http2StrAppend(&output, string_foo, kBar); + EXPECT_EQ("foobarfoobarfoobarfoobar", output); + + output.clear(); + + Http2StrAppend(&output, string_foo, string_bar); + EXPECT_EQ("foobar", output); + Http2StrAppend(&output, string_foo, stringpiece_bar); + EXPECT_EQ("foobarfoobar", output); + Http2StrAppend(&output, stringpiece_foo, kBar); + EXPECT_EQ("foobarfoobarfoobar", output); + Http2StrAppend(&output, stringpiece_foo, string_bar); + EXPECT_EQ("foobarfoobarfoobarfoobar", output); + + output.clear(); + + Http2StrAppend(&output, stringpiece_foo, stringpiece_bar); + EXPECT_EQ("foobar", output); + + // Many-many arguments. + Http2StrAppend(&output, "foo", "bar", "baz", "qux", "quux", "quuz", "corge", + "grault", "garply", "waldo", "fred", "plugh", "xyzzy", "thud"); + EXPECT_EQ( + "foobarfoobarbazquxquuxquuzcorgegraultgarplywaldofredplughxyzzythud", + output); + + output.clear(); + + // Numerical arguments. + const int16_t i = 1; + const uint64_t u = 8; + const double d = 3.1415; + + Http2StrAppend(&output, i, " ", u); + EXPECT_EQ("1 8", output); + Http2StrAppend(&output, d, i, i, u, i); + EXPECT_EQ("1 83.14151181", output); + Http2StrAppend(&output, "i: ", i, ", u: ", u, ", d: ", d); + EXPECT_EQ("1 83.14151181i: 1, u: 8, d: 3.1415", output); + + output.clear(); + + // Boolean arguments. + const bool t = true; + const bool f = false; + + Http2StrAppend(&output, t); + EXPECT_EQ("1", output); + Http2StrAppend(&output, f); + EXPECT_EQ("10", output); + Http2StrAppend(&output, f, t, t, f); + EXPECT_EQ("100110", output); + + output.clear(); + + // Mixed string-like, numerical, and Boolean arguments. + Http2StrAppend(&output, kFoo, i, string_foo, f, u, t, stringpiece_bar, d, t); + EXPECT_EQ("foo1foo081bar3.14151", output); + Http2StrAppend(&output, d, t, t, string_bar, i, u, kBar, t, d, f); + EXPECT_EQ("foo1foo081bar3.141513.141511bar18bar13.14150", output); +} + +TEST(Http2StringUtilsTest, Http2StringPrintf) { + EXPECT_EQ("", Http2StringPrintf("%s", "")); + EXPECT_EQ("foobar", Http2StringPrintf("%sbar", "foo")); + EXPECT_EQ("foobar", Http2StringPrintf("%s%s", "foo", "bar")); + EXPECT_EQ("foo: 1, bar: 2.0", + Http2StringPrintf("foo: %d, bar: %.1f", 1, 2.0)); +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/platform/api/http2_test_helpers.h b/http2/platform/api/http2_test_helpers.h new file mode 100644 index 0000000..a54f4ab --- /dev/null +++ b/http2/platform/api/http2_test_helpers.h
@@ -0,0 +1,16 @@ +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_TEST_HELPERS_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_TEST_HELPERS_H_ + +// Provides VERIFY_* macros, similar to EXPECT_* and ASSERT_*, but they return +// an AssertionResult if the condition is not satisfied. +#include "net/http2/platform/impl/http2_test_helpers_impl.h" + +#include "testing/gtest/include/gtest/gtest.h" // For AssertionSuccess + +#define VERIFY_AND_RETURN_SUCCESS(expression) \ + { \ + VERIFY_SUCCESS(expression); \ + return ::testing::AssertionSuccess(); \ + } + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_TEST_HELPERS_H_
diff --git a/http2/test_tools/frame_parts.cc b/http2/test_tools/frame_parts.cc new file mode 100644 index 0000000..3d0453e --- /dev/null +++ b/http2/test_tools/frame_parts.cc
@@ -0,0 +1,526 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h" + +#include <type_traits> + +#include "base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/http2_structures_test_util.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" + +using ::testing::AssertionFailure; +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using ::testing::ContainerEq; + +namespace http2 { +namespace test { +namespace { + +static_assert(std::is_base_of<Http2FrameDecoderListener, FrameParts>::value && + !std::is_abstract<FrameParts>::value, + "FrameParts needs to implement all of the methods of " + "Http2FrameDecoderListener"); + +// Compare two optional variables of the same type. +// TODO(jamessynge): Maybe create a ::testing::Matcher for this. +template <class T> +AssertionResult VerifyOptionalEq(const T& opt_a, const T& opt_b) { + if (opt_a) { + if (opt_b) { + VERIFY_EQ(opt_a.value(), opt_b.value()); + } else { + return AssertionFailure() + << "opt_b is not set; opt_a.value()=" << opt_a.value(); + } + } else if (opt_b) { + return AssertionFailure() + << "opt_a is not set; opt_b.value()=" << opt_b.value(); + } + return AssertionSuccess(); +} + +} // namespace + +FrameParts::FrameParts(const Http2FrameHeader& header) : frame_header_(header) { + VLOG(1) << "FrameParts, header: " << frame_header_; +} + +FrameParts::FrameParts(const Http2FrameHeader& header, Http2StringPiece payload) + : FrameParts(header) { + VLOG(1) << "FrameParts with payload.size() = " << payload.size(); + this->payload_.append(payload.data(), payload.size()); + opt_payload_length_ = payload.size(); +} +FrameParts::FrameParts(const Http2FrameHeader& header, + Http2StringPiece payload, + size_t total_pad_length) + : FrameParts(header, payload) { + VLOG(1) << "FrameParts with total_pad_length=" << total_pad_length; + SetTotalPadLength(total_pad_length); +} + +FrameParts::FrameParts(const FrameParts& header) = default; + +FrameParts::~FrameParts() = default; + +AssertionResult FrameParts::VerifyEquals(const FrameParts& that) const { +#define COMMON_MESSAGE "\n this: " << *this << "\n that: " << that + + VERIFY_EQ(frame_header_, that.frame_header_) << COMMON_MESSAGE; + VERIFY_EQ(payload_, that.payload_) << COMMON_MESSAGE; + VERIFY_EQ(padding_, that.padding_) << COMMON_MESSAGE; + VERIFY_EQ(altsvc_origin_, that.altsvc_origin_) << COMMON_MESSAGE; + VERIFY_EQ(altsvc_value_, that.altsvc_value_) << COMMON_MESSAGE; + VERIFY_THAT(settings_, ContainerEq(that.settings_)) << COMMON_MESSAGE; + +#define VERIFY_OPTIONAL_FIELD(field_name) \ + VERIFY_SUCCESS(VerifyOptionalEq(field_name, that.field_name)) + + VERIFY_OPTIONAL_FIELD(opt_altsvc_origin_length_) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_altsvc_value_length_) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_goaway_) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_missing_length_) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_pad_length_) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_ping_) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_priority_) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_push_promise_) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_rst_stream_error_code_) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_window_update_increment_) << COMMON_MESSAGE; + +#undef VERIFY_OPTIONAL_FIELD + + return AssertionSuccess(); +} + +void FrameParts::SetTotalPadLength(size_t total_pad_length) { + opt_pad_length_.reset(); + padding_.clear(); + if (total_pad_length > 0) { + ASSERT_LE(total_pad_length, 256u); + ASSERT_TRUE(frame_header_.IsPadded()); + opt_pad_length_ = total_pad_length - 1; + char zero = 0; + padding_.append(opt_pad_length_.value(), zero); + } + + if (opt_pad_length_) { + VLOG(1) << "SetTotalPadLength: pad_length=" << opt_pad_length_.value(); + } else { + VLOG(1) << "SetTotalPadLength: has no pad length"; + } +} + +void FrameParts::SetAltSvcExpected(Http2StringPiece origin, + Http2StringPiece value) { + altsvc_origin_.append(origin.data(), origin.size()); + altsvc_value_.append(value.data(), value.size()); + opt_altsvc_origin_length_ = origin.size(); + opt_altsvc_value_length_ = value.size(); +} + +bool FrameParts::OnFrameHeader(const Http2FrameHeader& header) { + ADD_FAILURE() << "OnFrameHeader: " << *this; + return true; +} + +void FrameParts::OnDataStart(const Http2FrameHeader& header) { + VLOG(1) << "OnDataStart: " << header; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::DATA)) << *this; + opt_payload_length_ = header.payload_length; +} + +void FrameParts::OnDataPayload(const char* data, size_t len) { + VLOG(1) << "OnDataPayload: len=" << len + << "; frame_header_: " << frame_header_; + ASSERT_TRUE(InFrameOfType(Http2FrameType::DATA)) << *this; + ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &payload_, + &opt_payload_length_)); +} + +void FrameParts::OnDataEnd() { + VLOG(1) << "OnDataEnd; frame_header_: " << frame_header_; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::DATA)) << *this; +} + +void FrameParts::OnHeadersStart(const Http2FrameHeader& header) { + VLOG(1) << "OnHeadersStart: " << header; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::HEADERS)) << *this; + opt_payload_length_ = header.payload_length; +} + +void FrameParts::OnHeadersPriority(const Http2PriorityFields& priority) { + VLOG(1) << "OnHeadersPriority: priority: " << priority + << "; frame_header_: " << frame_header_; + ASSERT_TRUE(InFrameOfType(Http2FrameType::HEADERS)) << *this; + ASSERT_FALSE(opt_priority_); + opt_priority_ = priority; + ASSERT_TRUE(opt_payload_length_); + opt_payload_length_ = + opt_payload_length_.value() - Http2PriorityFields::EncodedSize(); +} + +void FrameParts::OnHpackFragment(const char* data, size_t len) { + VLOG(1) << "OnHpackFragment: len=" << len + << "; frame_header_: " << frame_header_; + ASSERT_TRUE(got_start_callback_); + ASSERT_FALSE(got_end_callback_); + ASSERT_TRUE(FrameCanHaveHpackPayload(frame_header_)) << *this; + ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &payload_, + &opt_payload_length_)); +} + +void FrameParts::OnHeadersEnd() { + VLOG(1) << "OnHeadersEnd; frame_header_: " << frame_header_; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::HEADERS)) << *this; +} + +void FrameParts::OnPriorityFrame(const Http2FrameHeader& header, + const Http2PriorityFields& priority) { + VLOG(1) << "OnPriorityFrame: " << header << "; priority: " << priority; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::PRIORITY)) << *this; + ASSERT_FALSE(opt_priority_); + opt_priority_ = priority; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::PRIORITY)) << *this; +} + +void FrameParts::OnContinuationStart(const Http2FrameHeader& header) { + VLOG(1) << "OnContinuationStart: " << header; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::CONTINUATION)) << *this; + opt_payload_length_ = header.payload_length; +} + +void FrameParts::OnContinuationEnd() { + VLOG(1) << "OnContinuationEnd; frame_header_: " << frame_header_; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::CONTINUATION)) << *this; +} + +void FrameParts::OnPadLength(size_t trailing_length) { + VLOG(1) << "OnPadLength: trailing_length=" << trailing_length; + ASSERT_TRUE(InPaddedFrame()) << *this; + ASSERT_FALSE(opt_pad_length_); + ASSERT_TRUE(opt_payload_length_); + size_t total_padding_length = trailing_length + 1; + ASSERT_GE(opt_payload_length_.value(), total_padding_length); + opt_payload_length_ = opt_payload_length_.value() - total_padding_length; + opt_pad_length_ = trailing_length; +} + +void FrameParts::OnPadding(const char* pad, size_t skipped_length) { + VLOG(1) << "OnPadding: skipped_length=" << skipped_length; + ASSERT_TRUE(InPaddedFrame()) << *this; + ASSERT_TRUE(opt_pad_length_); + ASSERT_TRUE(AppendString(Http2StringPiece(pad, skipped_length), &padding_, + &opt_pad_length_)); +} + +void FrameParts::OnRstStream(const Http2FrameHeader& header, + Http2ErrorCode error_code) { + VLOG(1) << "OnRstStream: " << header << "; code=" << error_code; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::RST_STREAM)) << *this; + ASSERT_FALSE(opt_rst_stream_error_code_); + opt_rst_stream_error_code_ = error_code; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::RST_STREAM)) << *this; +} + +void FrameParts::OnSettingsStart(const Http2FrameHeader& header) { + VLOG(1) << "OnSettingsStart: " << header; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::SETTINGS)) << *this; + ASSERT_EQ(0u, settings_.size()); + ASSERT_FALSE(header.IsAck()) << header; +} + +void FrameParts::OnSetting(const Http2SettingFields& setting_fields) { + VLOG(1) << "OnSetting: " << setting_fields; + ASSERT_TRUE(InFrameOfType(Http2FrameType::SETTINGS)) << *this; + settings_.push_back(setting_fields); +} + +void FrameParts::OnSettingsEnd() { + VLOG(1) << "OnSettingsEnd; frame_header_: " << frame_header_; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::SETTINGS)) << *this; +} + +void FrameParts::OnSettingsAck(const Http2FrameHeader& header) { + VLOG(1) << "OnSettingsAck: " << header; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::SETTINGS)) << *this; + ASSERT_EQ(0u, settings_.size()); + ASSERT_TRUE(header.IsAck()); + ASSERT_TRUE(EndFrameOfType(Http2FrameType::SETTINGS)) << *this; +} + +void FrameParts::OnPushPromiseStart(const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) { + VLOG(1) << "OnPushPromiseStart header: " << header << "; promise: " << promise + << "; total_padding_length: " << total_padding_length; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::PUSH_PROMISE)) << *this; + ASSERT_GE(header.payload_length, Http2PushPromiseFields::EncodedSize()); + opt_payload_length_ = + header.payload_length - Http2PushPromiseFields::EncodedSize(); + ASSERT_FALSE(opt_push_promise_); + opt_push_promise_ = promise; + if (total_padding_length > 0) { + ASSERT_GE(opt_payload_length_.value(), total_padding_length); + OnPadLength(total_padding_length - 1); + } else { + ASSERT_FALSE(header.IsPadded()); + } +} + +void FrameParts::OnPushPromiseEnd() { + VLOG(1) << "OnPushPromiseEnd; frame_header_: " << frame_header_; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::PUSH_PROMISE)) << *this; +} + +void FrameParts::OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) { + VLOG(1) << "OnPing header: " << header << " ping: " << ping; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::PING)) << *this; + ASSERT_FALSE(header.IsAck()); + ASSERT_FALSE(opt_ping_); + opt_ping_ = ping; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::PING)) << *this; +} + +void FrameParts::OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) { + VLOG(1) << "OnPingAck header: " << header << " ping: " << ping; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::PING)) << *this; + ASSERT_TRUE(header.IsAck()); + ASSERT_FALSE(opt_ping_); + opt_ping_ = ping; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::PING)) << *this; +} + +void FrameParts::OnGoAwayStart(const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) { + VLOG(1) << "OnGoAwayStart: " << goaway; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::GOAWAY)) << *this; + ASSERT_FALSE(opt_goaway_); + opt_goaway_ = goaway; + opt_payload_length_ = + header.payload_length - Http2GoAwayFields::EncodedSize(); +} + +void FrameParts::OnGoAwayOpaqueData(const char* data, size_t len) { + VLOG(1) << "OnGoAwayOpaqueData: len=" << len; + ASSERT_TRUE(InFrameOfType(Http2FrameType::GOAWAY)) << *this; + ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &payload_, + &opt_payload_length_)); +} + +void FrameParts::OnGoAwayEnd() { + VLOG(1) << "OnGoAwayEnd; frame_header_: " << frame_header_; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::GOAWAY)) << *this; +} + +void FrameParts::OnWindowUpdate(const Http2FrameHeader& header, + uint32_t increment) { + VLOG(1) << "OnWindowUpdate header: " << header + << " increment=" << increment; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::WINDOW_UPDATE)) << *this; + ASSERT_FALSE(opt_window_update_increment_); + opt_window_update_increment_ = increment; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::WINDOW_UPDATE)) << *this; +} + +void FrameParts::OnAltSvcStart(const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) { + VLOG(1) << "OnAltSvcStart: " << header + << " origin_length: " << origin_length + << " value_length: " << value_length; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::ALTSVC)) << *this; + ASSERT_FALSE(opt_altsvc_origin_length_); + opt_altsvc_origin_length_ = origin_length; + ASSERT_FALSE(opt_altsvc_value_length_); + opt_altsvc_value_length_ = value_length; +} + +void FrameParts::OnAltSvcOriginData(const char* data, size_t len) { + VLOG(1) << "OnAltSvcOriginData: len=" << len; + ASSERT_TRUE(InFrameOfType(Http2FrameType::ALTSVC)) << *this; + ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &altsvc_origin_, + &opt_altsvc_origin_length_)); +} + +void FrameParts::OnAltSvcValueData(const char* data, size_t len) { + VLOG(1) << "OnAltSvcValueData: len=" << len; + ASSERT_TRUE(InFrameOfType(Http2FrameType::ALTSVC)) << *this; + ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &altsvc_value_, + &opt_altsvc_value_length_)); +} + +void FrameParts::OnAltSvcEnd() { + VLOG(1) << "OnAltSvcEnd; frame_header_: " << frame_header_; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::ALTSVC)) << *this; +} + +void FrameParts::OnUnknownStart(const Http2FrameHeader& header) { + VLOG(1) << "OnUnknownStart: " << header; + ASSERT_FALSE(IsSupportedHttp2FrameType(header.type)) << header; + ASSERT_FALSE(got_start_callback_); + ASSERT_EQ(frame_header_, header); + got_start_callback_ = true; + opt_payload_length_ = header.payload_length; +} + +void FrameParts::OnUnknownPayload(const char* data, size_t len) { + VLOG(1) << "OnUnknownPayload: len=" << len; + ASSERT_FALSE(IsSupportedHttp2FrameType(frame_header_.type)) << *this; + ASSERT_TRUE(got_start_callback_); + ASSERT_FALSE(got_end_callback_); + ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &payload_, + &opt_payload_length_)); +} + +void FrameParts::OnUnknownEnd() { + VLOG(1) << "OnUnknownEnd; frame_header_: " << frame_header_; + ASSERT_FALSE(IsSupportedHttp2FrameType(frame_header_.type)) << *this; + ASSERT_TRUE(got_start_callback_); + ASSERT_FALSE(got_end_callback_); + got_end_callback_ = true; +} + +void FrameParts::OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) { + VLOG(1) << "OnPaddingTooLong: " << header + << "; missing_length: " << missing_length; + ASSERT_EQ(frame_header_, header); + ASSERT_FALSE(got_end_callback_); + ASSERT_TRUE(FrameIsPadded(header)); + ASSERT_FALSE(opt_pad_length_); + ASSERT_FALSE(opt_missing_length_); + opt_missing_length_ = missing_length; + got_start_callback_ = true; + got_end_callback_ = true; +} + +void FrameParts::OnFrameSizeError(const Http2FrameHeader& header) { + VLOG(1) << "OnFrameSizeError: " << header; + ASSERT_EQ(frame_header_, header); + ASSERT_FALSE(got_end_callback_); + ASSERT_FALSE(has_frame_size_error_); + has_frame_size_error_ = true; + got_end_callback_ = true; +} + +void FrameParts::OutputTo(std::ostream& out) const { + out << "FrameParts{\n frame_header_: " << frame_header_ << "\n"; + if (!payload_.empty()) { + out << " payload_=\"" << Http2HexEscape(payload_) << "\"\n"; + } + if (!padding_.empty()) { + out << " padding_=\"" << Http2HexEscape(padding_) << "\"\n"; + } + if (!altsvc_origin_.empty()) { + out << " altsvc_origin_=\"" << Http2HexEscape(altsvc_origin_) << "\"\n"; + } + if (!altsvc_value_.empty()) { + out << " altsvc_value_=\"" << Http2HexEscape(altsvc_value_) << "\"\n"; + } + if (opt_priority_) { + out << " priority=" << opt_priority_.value() << "\n"; + } + if (opt_rst_stream_error_code_) { + out << " rst_stream=" << opt_rst_stream_error_code_.value() << "\n"; + } + if (opt_push_promise_) { + out << " push_promise=" << opt_push_promise_.value() << "\n"; + } + if (opt_ping_) { + out << " ping=" << opt_ping_.value() << "\n"; + } + if (opt_goaway_) { + out << " goaway=" << opt_goaway_.value() << "\n"; + } + if (opt_window_update_increment_) { + out << " window_update=" << opt_window_update_increment_.value() << "\n"; + } + if (opt_payload_length_) { + out << " payload_length=" << opt_payload_length_.value() << "\n"; + } + if (opt_pad_length_) { + out << " pad_length=" << opt_pad_length_.value() << "\n"; + } + if (opt_missing_length_) { + out << " missing_length=" << opt_missing_length_.value() << "\n"; + } + if (opt_altsvc_origin_length_) { + out << " origin_length=" << opt_altsvc_origin_length_.value() << "\n"; + } + if (opt_altsvc_value_length_) { + out << " value_length=" << opt_altsvc_value_length_.value() << "\n"; + } + if (has_frame_size_error_) { + out << " has_frame_size_error\n"; + } + if (got_start_callback_) { + out << " got_start_callback\n"; + } + if (got_end_callback_) { + out << " got_end_callback\n"; + } + for (size_t ndx = 0; ndx < settings_.size(); ++ndx) { + out << " setting[" << ndx << "]=" << settings_[ndx]; + } + out << "}"; +} + +AssertionResult FrameParts::StartFrameOfType( + const Http2FrameHeader& header, + Http2FrameType expected_frame_type) { + VERIFY_EQ(header.type, expected_frame_type); + VERIFY_FALSE(got_start_callback_); + VERIFY_FALSE(got_end_callback_); + VERIFY_EQ(frame_header_, header); + got_start_callback_ = true; + return AssertionSuccess(); +} + +AssertionResult FrameParts::InFrameOfType(Http2FrameType expected_frame_type) { + VERIFY_TRUE(got_start_callback_); + VERIFY_FALSE(got_end_callback_); + VERIFY_EQ(frame_header_.type, expected_frame_type); + return AssertionSuccess(); +} + +AssertionResult FrameParts::EndFrameOfType(Http2FrameType expected_frame_type) { + VERIFY_SUCCESS(InFrameOfType(expected_frame_type)); + got_end_callback_ = true; + return AssertionSuccess(); +} + +AssertionResult FrameParts::InPaddedFrame() { + VERIFY_TRUE(got_start_callback_); + VERIFY_FALSE(got_end_callback_); + VERIFY_TRUE(FrameIsPadded(frame_header_)); + return AssertionSuccess(); +} + +AssertionResult FrameParts::AppendString(Http2StringPiece source, + Http2String* target, + Http2Optional<size_t>* opt_length) { + target->append(source.data(), source.size()); + if (opt_length != nullptr) { + VERIFY_TRUE(*opt_length) << "Length is not set yet\n" << *this; + VERIFY_LE(target->size(), opt_length->value()) + << "String too large; source.size() = " << source.size() << "\n" + << *this; + } + return ::testing::AssertionSuccess(); +} + +std::ostream& operator<<(std::ostream& out, const FrameParts& v) { + v.OutputTo(out); + return out; +} + +} // namespace test +} // namespace http2
diff --git a/http2/test_tools/frame_parts.h b/http2/test_tools/frame_parts.h new file mode 100644 index 0000000..b531174 --- /dev/null +++ b/http2/test_tools/frame_parts.h
@@ -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. + +#ifndef QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_H_ +#define QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_H_ + +// FrameParts implements Http2FrameDecoderListener, recording the callbacks +// during the decoding of a single frame. It is also used for comparing the +// info that a test expects to be recorded during the decoding of a frame +// with the actual recorded value (i.e. by providing a comparator). + +#include <stddef.h> + +#include <cstdint> +#include <vector> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_optional.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { +namespace test { + +class FrameParts : public Http2FrameDecoderListener { + public: + // The first callback for every type of frame includes the frame header; this + // is the only constructor used during decoding of a frame. + explicit FrameParts(const Http2FrameHeader& header); + + // For use in tests where the expected frame has a variable size payload. + FrameParts(const Http2FrameHeader& header, Http2StringPiece payload); + + // For use in tests where the expected frame has a variable size payload + // and may be padded. + FrameParts(const Http2FrameHeader& header, + Http2StringPiece payload, + size_t total_pad_length); + + // Copy constructor. + FrameParts(const FrameParts& header); + + ~FrameParts() override; + + // Returns AssertionSuccess() if they're equal, else AssertionFailure() + // with info about the difference. + ::testing::AssertionResult VerifyEquals(const FrameParts& other) const; + + // Format this FrameParts object. + void OutputTo(std::ostream& out) const; + + // Set the total padding length (0 to 256). + void SetTotalPadLength(size_t total_pad_length); + + // Set the origin and value expected in an ALTSVC frame. + void SetAltSvcExpected(Http2StringPiece origin, Http2StringPiece value); + + // Http2FrameDecoderListener methods: + bool OnFrameHeader(const Http2FrameHeader& header) override; + void OnDataStart(const Http2FrameHeader& header) override; + void OnDataPayload(const char* data, size_t len) override; + void OnDataEnd() override; + void OnHeadersStart(const Http2FrameHeader& header) override; + void OnHeadersPriority(const Http2PriorityFields& priority) override; + void OnHpackFragment(const char* data, size_t len) override; + void OnHeadersEnd() override; + void OnPriorityFrame(const Http2FrameHeader& header, + const Http2PriorityFields& priority) override; + void OnContinuationStart(const Http2FrameHeader& header) override; + void OnContinuationEnd() override; + void OnPadLength(size_t trailing_length) override; + void OnPadding(const char* pad, size_t skipped_length) override; + void OnRstStream(const Http2FrameHeader& header, + Http2ErrorCode error_code) override; + void OnSettingsStart(const Http2FrameHeader& header) override; + void OnSetting(const Http2SettingFields& setting_fields) override; + void OnSettingsEnd() override; + void OnSettingsAck(const Http2FrameHeader& header) override; + void OnPushPromiseStart(const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) override; + void OnPushPromiseEnd() override; + void OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) override; + void OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) override; + void OnGoAwayStart(const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) override; + void OnGoAwayOpaqueData(const char* data, size_t len) override; + void OnGoAwayEnd() override; + void OnWindowUpdate(const Http2FrameHeader& header, + uint32_t increment) override; + void OnAltSvcStart(const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) override; + void OnAltSvcOriginData(const char* data, size_t len) override; + void OnAltSvcValueData(const char* data, size_t len) override; + void OnAltSvcEnd() override; + void OnUnknownStart(const Http2FrameHeader& header) override; + void OnUnknownPayload(const char* data, size_t len) override; + void OnUnknownEnd() override; + void OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) override; + void OnFrameSizeError(const Http2FrameHeader& header) override; + + void AppendSetting(const Http2SettingFields& setting_fields) { + settings_.push_back(setting_fields); + } + + const Http2FrameHeader& GetFrameHeader() const { return frame_header_; } + + Http2Optional<Http2PriorityFields> GetOptPriority() const { + return opt_priority_; + } + Http2Optional<Http2ErrorCode> GetOptRstStreamErrorCode() const { + return opt_rst_stream_error_code_; + } + Http2Optional<Http2PushPromiseFields> GetOptPushPromise() const { + return opt_push_promise_; + } + Http2Optional<Http2PingFields> GetOptPing() const { return opt_ping_; } + Http2Optional<Http2GoAwayFields> GetOptGoaway() const { return opt_goaway_; } + Http2Optional<size_t> GetOptPadLength() const { return opt_pad_length_; } + Http2Optional<size_t> GetOptPayloadLength() const { + return opt_payload_length_; + } + Http2Optional<size_t> GetOptMissingLength() const { + return opt_missing_length_; + } + Http2Optional<size_t> GetOptAltsvcOriginLength() const { + return opt_altsvc_origin_length_; + } + Http2Optional<size_t> GetOptAltsvcValueLength() const { + return opt_altsvc_value_length_; + } + Http2Optional<size_t> GetOptWindowUpdateIncrement() const { + return opt_window_update_increment_; + } + bool GetHasFrameSizeError() const { return has_frame_size_error_; } + + void SetOptPriority(Http2Optional<Http2PriorityFields> opt_priority) { + opt_priority_ = opt_priority; + } + void SetOptRstStreamErrorCode( + Http2Optional<Http2ErrorCode> opt_rst_stream_error_code) { + opt_rst_stream_error_code_ = opt_rst_stream_error_code; + } + void SetOptPushPromise( + Http2Optional<Http2PushPromiseFields> opt_push_promise) { + opt_push_promise_ = opt_push_promise; + } + void SetOptPing(Http2Optional<Http2PingFields> opt_ping) { + opt_ping_ = opt_ping; + } + void SetOptGoaway(Http2Optional<Http2GoAwayFields> opt_goaway) { + opt_goaway_ = opt_goaway; + } + void SetOptPadLength(Http2Optional<size_t> opt_pad_length) { + opt_pad_length_ = opt_pad_length; + } + void SetOptPayloadLength(Http2Optional<size_t> opt_payload_length) { + opt_payload_length_ = opt_payload_length; + } + void SetOptMissingLength(Http2Optional<size_t> opt_missing_length) { + opt_missing_length_ = opt_missing_length; + } + void SetOptAltsvcOriginLength( + Http2Optional<size_t> opt_altsvc_origin_length) { + opt_altsvc_origin_length_ = opt_altsvc_origin_length; + } + void SetOptAltsvcValueLength(Http2Optional<size_t> opt_altsvc_value_length) { + opt_altsvc_value_length_ = opt_altsvc_value_length; + } + void SetOptWindowUpdateIncrement( + Http2Optional<size_t> opt_window_update_increment) { + opt_window_update_increment_ = opt_window_update_increment; + } + + void SetHasFrameSizeError(bool has_frame_size_error) { + has_frame_size_error_ = has_frame_size_error; + } + + private: + // ASSERT during an On* method that we're handling a frame of type + // expected_frame_type, and have not already received other On* methods + // (i.e. got_start_callback is false). + ::testing::AssertionResult StartFrameOfType( + const Http2FrameHeader& header, + Http2FrameType expected_frame_type); + + // ASSERT that StartFrameOfType has already been called with + // expected_frame_type (i.e. got_start_callback has been called), and that + // EndFrameOfType has not yet been called (i.e. got_end_callback is false). + ::testing::AssertionResult InFrameOfType(Http2FrameType expected_frame_type); + + // ASSERT that we're InFrameOfType, and then sets got_end_callback=true. + ::testing::AssertionResult EndFrameOfType(Http2FrameType expected_frame_type); + + // ASSERT that we're in the middle of processing a frame that is padded. + ::testing::AssertionResult InPaddedFrame(); + + // Append source to target. If opt_length is not nullptr, then verifies that + // the optional has a value (i.e. that the necessary On*Start method has been + // called), and that target is not longer than opt_length->value(). + ::testing::AssertionResult AppendString(Http2StringPiece source, + Http2String* target, + Http2Optional<size_t>* opt_length); + + const Http2FrameHeader frame_header_; + + Http2String payload_; + Http2String padding_; + Http2String altsvc_origin_; + Http2String altsvc_value_; + + Http2Optional<Http2PriorityFields> opt_priority_; + Http2Optional<Http2ErrorCode> opt_rst_stream_error_code_; + Http2Optional<Http2PushPromiseFields> opt_push_promise_; + Http2Optional<Http2PingFields> opt_ping_; + Http2Optional<Http2GoAwayFields> opt_goaway_; + + Http2Optional<size_t> opt_pad_length_; + Http2Optional<size_t> opt_payload_length_; + Http2Optional<size_t> opt_missing_length_; + Http2Optional<size_t> opt_altsvc_origin_length_; + Http2Optional<size_t> opt_altsvc_value_length_; + + Http2Optional<size_t> opt_window_update_increment_; + + bool has_frame_size_error_ = false; + + std::vector<Http2SettingFields> settings_; + + // These booleans are not checked by CompareCollectedFrames. + bool got_start_callback_ = false; + bool got_end_callback_ = false; +}; + +std::ostream& operator<<(std::ostream& out, const FrameParts& v); + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_H_
diff --git a/http2/test_tools/frame_parts_collector.cc b/http2/test_tools/frame_parts_collector.cc new file mode 100644 index 0000000..be4d986 --- /dev/null +++ b/http2/test_tools/frame_parts_collector.cc
@@ -0,0 +1,113 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h" + +#include <utility> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/http2_structures_test_util.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_ptr_util.h" + +namespace http2 { +namespace test { + +FramePartsCollector::FramePartsCollector() = default; +FramePartsCollector::~FramePartsCollector() = default; + +void FramePartsCollector::Reset() { + current_frame_.reset(); + collected_frames_.clear(); + expected_header_set_ = false; +} + +const FrameParts* FramePartsCollector::frame(size_t n) const { + if (n < size()) { + return collected_frames_.at(n).get(); + } + CHECK(n == size()); + return current_frame(); +} + +void FramePartsCollector::ExpectFrameHeader(const Http2FrameHeader& header) { + EXPECT_FALSE(IsInProgress()); + EXPECT_FALSE(expected_header_set_) + << "expected_header_: " << expected_header_; + expected_header_ = header; + expected_header_set_ = true; + // OnFrameHeader is called before the flags are scrubbed, but the other + // methods are called after, so scrub the invalid flags from expected_header_. + ScrubFlagsOfHeader(&expected_header_); +} + +void FramePartsCollector::TestExpectedHeader(const Http2FrameHeader& header) { + if (expected_header_set_) { + EXPECT_EQ(header, expected_header_); + expected_header_set_ = false; + } +} + +Http2FrameDecoderListener* FramePartsCollector::StartFrame( + const Http2FrameHeader& header) { + TestExpectedHeader(header); + EXPECT_FALSE(IsInProgress()); + if (current_frame_ == nullptr) { + current_frame_ = Http2MakeUnique<FrameParts>(header); + } + return current_frame(); +} + +Http2FrameDecoderListener* FramePartsCollector::StartAndEndFrame( + const Http2FrameHeader& header) { + TestExpectedHeader(header); + EXPECT_FALSE(IsInProgress()); + if (current_frame_ == nullptr) { + current_frame_ = Http2MakeUnique<FrameParts>(header); + } + Http2FrameDecoderListener* result = current_frame(); + collected_frames_.push_back(std::move(current_frame_)); + return result; +} + +Http2FrameDecoderListener* FramePartsCollector::CurrentFrame() { + EXPECT_TRUE(IsInProgress()); + if (current_frame_ == nullptr) { + return &failing_listener_; + } + return current_frame(); +} + +Http2FrameDecoderListener* FramePartsCollector::EndFrame() { + EXPECT_TRUE(IsInProgress()); + if (current_frame_ == nullptr) { + return &failing_listener_; + } + Http2FrameDecoderListener* result = current_frame(); + collected_frames_.push_back(std::move(current_frame_)); + return result; +} + +Http2FrameDecoderListener* FramePartsCollector::FrameError( + const Http2FrameHeader& header) { + TestExpectedHeader(header); + if (current_frame_ == nullptr) { + // The decoder may detect an error before making any calls to the listener + // regarding the frame, in which case current_frame_==nullptr and we need + // to create a FrameParts instance. + current_frame_ = Http2MakeUnique<FrameParts>(header); + } else { + // Similarly, the decoder may have made calls to the listener regarding the + // frame before detecting the error; for example, the DATA payload decoder + // calls OnDataStart before it can detect padding errors, hence before it + // can call OnPaddingTooLong. + EXPECT_EQ(header, current_frame_->GetFrameHeader()); + } + Http2FrameDecoderListener* result = current_frame(); + collected_frames_.push_back(std::move(current_frame_)); + return result; +} + +} // namespace test +} // namespace http2
diff --git a/http2/test_tools/frame_parts_collector.h b/http2/test_tools/frame_parts_collector.h new file mode 100644 index 0000000..a35740a --- /dev/null +++ b/http2/test_tools/frame_parts_collector.h
@@ -0,0 +1,111 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_COLLECTOR_H_ +#define QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_COLLECTOR_H_ + +// FramePartsCollector is a base class for Http2FrameDecoderListener +// implementations that create one FrameParts instance for each decoded frame. + +#include <stddef.h> + +#include <memory> +#include <vector> + +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener_test_util.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h" + +namespace http2 { +namespace test { + +class FramePartsCollector : public FailingHttp2FrameDecoderListener { + public: + FramePartsCollector(); + ~FramePartsCollector() override; + + // Toss out the collected data. + void Reset(); + + // Returns true if has started recording the info for a frame and has not yet + // finished doing so. + bool IsInProgress() const { return current_frame_ != nullptr; } + + // Returns the FrameParts instance into which we're currently recording + // callback info if IsInProgress, else nullptr. + const FrameParts* current_frame() const { return current_frame_.get(); } + + // Returns the number of completely collected FrameParts instances. + size_t size() const { return collected_frames_.size(); } + + // Returns the n'th frame, where 0 is the oldest of the collected frames, + // and n==size() is the frame currently being collected, if there is one. + // Returns nullptr if the requested index is not valid. + const FrameParts* frame(size_t n) const; + + protected: + // In support of OnFrameHeader, set the header that we expect to be used in + // the next call. + // TODO(jamessynge): Remove ExpectFrameHeader et al. once done with supporting + // SpdyFramer's exact states. + void ExpectFrameHeader(const Http2FrameHeader& header); + + // For use in implementing On*Start methods of Http2FrameDecoderListener, + // returns a FrameParts instance, which will be newly created if + // IsInProgress==false (which the caller should ensure), else will be the + // current_frame(); never returns nullptr. + // If called when IsInProgress==true, a test failure will be recorded. + Http2FrameDecoderListener* StartFrame(const Http2FrameHeader& header); + + // For use in implementing On* callbacks, such as OnPingAck, that are the only + // call expected for the frame being decoded; not for On*Start methods. + // Returns a FrameParts instance, which will be newly created if + // IsInProgress==false (which the caller should ensure), else will be the + // current_frame(); never returns nullptr. + // If called when IsInProgress==true, a test failure will be recorded. + Http2FrameDecoderListener* StartAndEndFrame(const Http2FrameHeader& header); + + // If IsInProgress==true, returns the FrameParts into which the current + // frame is being recorded; else records a test failure and returns + // failing_listener_, which will record a test failure when any of its + // On* methods is called. + Http2FrameDecoderListener* CurrentFrame(); + + // For use in implementing On*End methods, pushes the current frame onto + // the vector of completed frames, and returns a pointer to it for recording + // the info in the final call. If IsInProgress==false, records a test failure + // and returns failing_listener_, which will record a test failure when any + // of its On* methods is called. + Http2FrameDecoderListener* EndFrame(); + + // For use in implementing OnPaddingTooLong and OnFrameSizeError, is + // equivalent to EndFrame() if IsInProgress==true, else equivalent to + // StartAndEndFrame(). + Http2FrameDecoderListener* FrameError(const Http2FrameHeader& header); + + private: + // Returns the mutable FrameParts instance into which we're currently + // recording callback info if IsInProgress, else nullptr. + FrameParts* current_frame() { return current_frame_.get(); } + + // If expected header is set, verify that it matches the header param. + // TODO(jamessynge): Remove TestExpectedHeader et al. once done + // with supporting SpdyFramer's exact states. + void TestExpectedHeader(const Http2FrameHeader& header); + + std::unique_ptr<FrameParts> current_frame_; + std::vector<std::unique_ptr<FrameParts>> collected_frames_; + FailingHttp2FrameDecoderListener failing_listener_; + + // TODO(jamessynge): Remove expected_header_ et al. once done with supporting + // SpdyFramer's exact states. + Http2FrameHeader expected_header_; + bool expected_header_set_ = false; +}; + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_COLLECTOR_H_
diff --git a/http2/test_tools/frame_parts_collector_listener.cc b/http2/test_tools/frame_parts_collector_listener.cc new file mode 100644 index 0000000..8d9da78 --- /dev/null +++ b/http2/test_tools/frame_parts_collector_listener.cc
@@ -0,0 +1,230 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.h" + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace http2 { +namespace test { + +bool FramePartsCollectorListener::OnFrameHeader( + const Http2FrameHeader& header) { + VLOG(1) << "OnFrameHeader: " << header; + ExpectFrameHeader(header); + return true; +} + +void FramePartsCollectorListener::OnDataStart(const Http2FrameHeader& header) { + VLOG(1) << "OnDataStart: " << header; + StartFrame(header)->OnDataStart(header); +} + +void FramePartsCollectorListener::OnDataPayload(const char* data, size_t len) { + VLOG(1) << "OnDataPayload: len=" << len; + CurrentFrame()->OnDataPayload(data, len); +} + +void FramePartsCollectorListener::OnDataEnd() { + VLOG(1) << "OnDataEnd"; + EndFrame()->OnDataEnd(); +} + +void FramePartsCollectorListener::OnHeadersStart( + const Http2FrameHeader& header) { + VLOG(1) << "OnHeadersStart: " << header; + StartFrame(header)->OnHeadersStart(header); +} + +void FramePartsCollectorListener::OnHeadersPriority( + const Http2PriorityFields& priority) { + VLOG(1) << "OnHeadersPriority: " << priority; + CurrentFrame()->OnHeadersPriority(priority); +} + +void FramePartsCollectorListener::OnHpackFragment(const char* data, + size_t len) { + VLOG(1) << "OnHpackFragment: len=" << len; + CurrentFrame()->OnHpackFragment(data, len); +} + +void FramePartsCollectorListener::OnHeadersEnd() { + VLOG(1) << "OnHeadersEnd"; + EndFrame()->OnHeadersEnd(); +} + +void FramePartsCollectorListener::OnPriorityFrame( + const Http2FrameHeader& header, + const Http2PriorityFields& priority_fields) { + VLOG(1) << "OnPriority: " << header << "; " << priority_fields; + StartAndEndFrame(header)->OnPriorityFrame(header, priority_fields); +} + +void FramePartsCollectorListener::OnContinuationStart( + const Http2FrameHeader& header) { + VLOG(1) << "OnContinuationStart: " << header; + StartFrame(header)->OnContinuationStart(header); +} + +void FramePartsCollectorListener::OnContinuationEnd() { + VLOG(1) << "OnContinuationEnd"; + EndFrame()->OnContinuationEnd(); +} + +void FramePartsCollectorListener::OnPadLength(size_t pad_length) { + VLOG(1) << "OnPadLength: " << pad_length; + CurrentFrame()->OnPadLength(pad_length); +} + +void FramePartsCollectorListener::OnPadding(const char* padding, + size_t skipped_length) { + VLOG(1) << "OnPadding: " << skipped_length; + CurrentFrame()->OnPadding(padding, skipped_length); +} + +void FramePartsCollectorListener::OnRstStream(const Http2FrameHeader& header, + Http2ErrorCode error_code) { + VLOG(1) << "OnRstStream: " << header << "; error_code=" << error_code; + StartAndEndFrame(header)->OnRstStream(header, error_code); +} + +void FramePartsCollectorListener::OnSettingsStart( + const Http2FrameHeader& header) { + VLOG(1) << "OnSettingsStart: " << header; + EXPECT_EQ(Http2FrameType::SETTINGS, header.type) << header; + EXPECT_EQ(Http2FrameFlag(), header.flags) << header; + StartFrame(header)->OnSettingsStart(header); +} + +void FramePartsCollectorListener::OnSetting( + const Http2SettingFields& setting_fields) { + VLOG(1) << "Http2SettingFields: setting_fields=" << setting_fields; + CurrentFrame()->OnSetting(setting_fields); +} + +void FramePartsCollectorListener::OnSettingsEnd() { + VLOG(1) << "OnSettingsEnd"; + EndFrame()->OnSettingsEnd(); +} + +void FramePartsCollectorListener::OnSettingsAck( + const Http2FrameHeader& header) { + VLOG(1) << "OnSettingsAck: " << header; + StartAndEndFrame(header)->OnSettingsAck(header); +} + +void FramePartsCollectorListener::OnPushPromiseStart( + const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) { + VLOG(1) << "OnPushPromiseStart header: " << header << " promise: " << promise + << " total_padding_length: " << total_padding_length; + EXPECT_EQ(Http2FrameType::PUSH_PROMISE, header.type); + StartFrame(header)->OnPushPromiseStart(header, promise, total_padding_length); +} + +void FramePartsCollectorListener::OnPushPromiseEnd() { + VLOG(1) << "OnPushPromiseEnd"; + EndFrame()->OnPushPromiseEnd(); +} + +void FramePartsCollectorListener::OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) { + VLOG(1) << "OnPing: " << header << "; " << ping; + StartAndEndFrame(header)->OnPing(header, ping); +} + +void FramePartsCollectorListener::OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) { + VLOG(1) << "OnPingAck: " << header << "; " << ping; + StartAndEndFrame(header)->OnPingAck(header, ping); +} + +void FramePartsCollectorListener::OnGoAwayStart( + const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) { + VLOG(1) << "OnGoAwayStart header: " << header << "; goaway: " << goaway; + StartFrame(header)->OnGoAwayStart(header, goaway); +} + +void FramePartsCollectorListener::OnGoAwayOpaqueData(const char* data, + size_t len) { + VLOG(1) << "OnGoAwayOpaqueData: len=" << len; + CurrentFrame()->OnGoAwayOpaqueData(data, len); +} + +void FramePartsCollectorListener::OnGoAwayEnd() { + VLOG(1) << "OnGoAwayEnd"; + EndFrame()->OnGoAwayEnd(); +} + +void FramePartsCollectorListener::OnWindowUpdate( + const Http2FrameHeader& header, + uint32_t window_size_increment) { + VLOG(1) << "OnWindowUpdate: " << header + << "; window_size_increment=" << window_size_increment; + EXPECT_EQ(Http2FrameType::WINDOW_UPDATE, header.type); + StartAndEndFrame(header)->OnWindowUpdate(header, window_size_increment); +} + +void FramePartsCollectorListener::OnAltSvcStart(const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) { + VLOG(1) << "OnAltSvcStart header: " << header + << "; origin_length=" << origin_length + << "; value_length=" << value_length; + StartFrame(header)->OnAltSvcStart(header, origin_length, value_length); +} + +void FramePartsCollectorListener::OnAltSvcOriginData(const char* data, + size_t len) { + VLOG(1) << "OnAltSvcOriginData: len=" << len; + CurrentFrame()->OnAltSvcOriginData(data, len); +} + +void FramePartsCollectorListener::OnAltSvcValueData(const char* data, + size_t len) { + VLOG(1) << "OnAltSvcValueData: len=" << len; + CurrentFrame()->OnAltSvcValueData(data, len); +} + +void FramePartsCollectorListener::OnAltSvcEnd() { + VLOG(1) << "OnAltSvcEnd"; + EndFrame()->OnAltSvcEnd(); +} + +void FramePartsCollectorListener::OnUnknownStart( + const Http2FrameHeader& header) { + VLOG(1) << "OnUnknownStart: " << header; + StartFrame(header)->OnUnknownStart(header); +} + +void FramePartsCollectorListener::OnUnknownPayload(const char* data, + size_t len) { + VLOG(1) << "OnUnknownPayload: len=" << len; + CurrentFrame()->OnUnknownPayload(data, len); +} + +void FramePartsCollectorListener::OnUnknownEnd() { + VLOG(1) << "OnUnknownEnd"; + EndFrame()->OnUnknownEnd(); +} + +void FramePartsCollectorListener::OnPaddingTooLong( + const Http2FrameHeader& header, + size_t missing_length) { + VLOG(1) << "OnPaddingTooLong: " << header + << " missing_length: " << missing_length; + EndFrame()->OnPaddingTooLong(header, missing_length); +} + +void FramePartsCollectorListener::OnFrameSizeError( + const Http2FrameHeader& header) { + VLOG(1) << "OnFrameSizeError: " << header; + FrameError(header)->OnFrameSizeError(header); +} + +} // namespace test +} // namespace http2
diff --git a/http2/test_tools/frame_parts_collector_listener.h b/http2/test_tools/frame_parts_collector_listener.h new file mode 100644 index 0000000..07a82aa --- /dev/null +++ b/http2/test_tools/frame_parts_collector_listener.h
@@ -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. + +#ifndef QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_COLLECTOR_LISTENER_H_ +#define QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_COLLECTOR_LISTENER_H_ + +// FramePartsCollectorListener extends FramePartsCollector with an +// implementation of every method of Http2FrameDecoderListener; it is +// essentially the union of all the Listener classes in the tests of the +// payload decoders (i.e. in ./payload_decoders/*_test.cc files), with the +// addition of the OnFrameHeader method. +// FramePartsCollectorListener supports tests of Http2FrameDecoder. + +#include <stddef.h> + +#include <cstdint> + +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h" + +namespace http2 { +namespace test { + +class FramePartsCollectorListener : public FramePartsCollector { + public: + FramePartsCollectorListener() {} + ~FramePartsCollectorListener() override {} + + // TODO(jamessynge): Remove OnFrameHeader once done with supporting + // SpdyFramer's exact states. + bool OnFrameHeader(const Http2FrameHeader& header) override; + void OnDataStart(const Http2FrameHeader& header) override; + void OnDataPayload(const char* data, size_t len) override; + void OnDataEnd() override; + void OnHeadersStart(const Http2FrameHeader& header) override; + void OnHeadersPriority(const Http2PriorityFields& priority) override; + void OnHpackFragment(const char* data, size_t len) override; + void OnHeadersEnd() override; + void OnPriorityFrame(const Http2FrameHeader& header, + const Http2PriorityFields& priority_fields) override; + void OnContinuationStart(const Http2FrameHeader& header) override; + void OnContinuationEnd() override; + void OnPadLength(size_t pad_length) override; + void OnPadding(const char* padding, size_t skipped_length) override; + void OnRstStream(const Http2FrameHeader& header, + Http2ErrorCode error_code) override; + void OnSettingsStart(const Http2FrameHeader& header) override; + void OnSetting(const Http2SettingFields& setting_fields) override; + void OnSettingsEnd() override; + void OnSettingsAck(const Http2FrameHeader& header) override; + void OnPushPromiseStart(const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) override; + void OnPushPromiseEnd() override; + void OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) override; + void OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) override; + void OnGoAwayStart(const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) override; + void OnGoAwayOpaqueData(const char* data, size_t len) override; + void OnGoAwayEnd() override; + void OnWindowUpdate(const Http2FrameHeader& header, + uint32_t window_size_increment) override; + void OnAltSvcStart(const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) override; + void OnAltSvcOriginData(const char* data, size_t len) override; + void OnAltSvcValueData(const char* data, size_t len) override; + void OnAltSvcEnd() override; + void OnUnknownStart(const Http2FrameHeader& header) override; + void OnUnknownPayload(const char* data, size_t len) override; + void OnUnknownEnd() override; + void OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) override; + void OnFrameSizeError(const Http2FrameHeader& header) override; +}; + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_COLLECTOR_LISTENER_H_
diff --git a/http2/test_tools/http2_random.cc b/http2/test_tools/http2_random.cc new file mode 100644 index 0000000..fc577f4 --- /dev/null +++ b/http2/test_tools/http2_random.cc
@@ -0,0 +1,72 @@ +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" +#include "third_party/boringssl/src/include/openssl/chacha.h" +#include "third_party/boringssl/src/include/openssl/rand.h" + +static const uint8_t kZeroNonce[12] = {0}; + +namespace http2 { +namespace test { + +Http2Random::Http2Random() { + RAND_bytes(key_, sizeof(key_)); + + LOG(INFO) << "Initialized test RNG with the following key: " << Key(); +} + +Http2Random::Http2Random(Http2StringPiece key) { + Http2String decoded_key = Http2HexDecode(key); + CHECK_EQ(sizeof(key_), decoded_key.size()); + memcpy(key_, decoded_key.data(), sizeof(key_)); +} + +Http2String Http2Random::Key() const { + return Http2HexEncode(key_, sizeof(key_)); +} + +void Http2Random::FillRandom(void* buffer, size_t buffer_size) { + memset(buffer, 0, buffer_size); + uint8_t* buffer_u8 = reinterpret_cast<uint8_t*>(buffer); + CRYPTO_chacha_20(buffer_u8, buffer_u8, buffer_size, key_, kZeroNonce, + counter_++); +} + +Http2String Http2Random::RandString(int length) { + Http2String result; + result.resize(length); + FillRandom(&result[0], length); + return result; +} + +uint64_t Http2Random::Rand64() { + union { + uint64_t number; + uint8_t bytes[sizeof(uint64_t)]; + } result; + FillRandom(result.bytes, sizeof(result.bytes)); + return result.number; +} + +double Http2Random::RandDouble() { + union { + double f; + uint64_t i; + } value; + value.i = (1023ull << 52ull) | (Rand64() & 0xfffffffffffffu); + return value.f - 1.0; +} + +Http2String Http2Random::RandStringWithAlphabet(int length, + Http2StringPiece alphabet) { + Http2String result; + result.resize(length); + for (int i = 0; i < length; i++) { + result[i] = alphabet[Uniform(alphabet.size())]; + } + return result; +} + +} // namespace test +} // namespace http2
diff --git a/http2/test_tools/http2_random.h b/http2/test_tools/http2_random.h new file mode 100644 index 0000000..def60f8 --- /dev/null +++ b/http2/test_tools/http2_random.h
@@ -0,0 +1,88 @@ +// 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. + +#ifndef QUICHE_HTTP2_TEST_TOOLS_HTTP2_RANDOM_H_ +#define QUICHE_HTTP2_TEST_TOOLS_HTTP2_RANDOM_H_ + +#include <cmath> +#include <cstdint> +#include <limits> +#include <random> + +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { +namespace test { + +// The random number generator used for unit tests. Since the algorithm is +// deterministic and fixed, this can be used to reproduce flakes in the unit +// tests caused by specific random values. +class Http2Random { + public: + Http2Random(); + + Http2Random(const Http2Random&) = delete; + Http2Random& operator=(const Http2Random&) = delete; + + // Reproducible random number generation: by using the same key, the same + // sequence of results is obtained. + explicit Http2Random(Http2StringPiece key); + Http2String Key() const; + + void FillRandom(void* buffer, size_t buffer_size); + Http2String RandString(int length); + + // Returns a random 64-bit value. + uint64_t Rand64(); + + // Return a uniformly distrubted random number in [0, n). + uint32_t Uniform(uint32_t n) { return Rand64() % n; } + // Return a uniformly distrubted random number in [lo, hi). + uint64_t UniformInRange(uint64_t lo, uint64_t hi) { + return lo + Rand64() % (hi - lo); + } + // Return an integer of logarithmically random scale. + uint32_t Skewed(uint32_t max_log) { + const uint32_t base = Rand32() % (max_log + 1); + const uint32_t mask = ((base < 32) ? (1u << base) : 0u) - 1u; + return Rand32() & mask; + } + // Return a random number in [0, max] range that skews low. + uint64_t RandomSizeSkewedLow(uint64_t max) { + return std::round(max * std::pow(RandDouble(), 2)); + } + + // Returns a random double between 0 and 1. + double RandDouble(); + float RandFloat() { return RandDouble(); } + + // Has 1/n chance of returning true. + bool OneIn(int n) { return Uniform(n) == 0; } + + uint8_t Rand8() { return Rand64(); } + uint16_t Rand16() { return Rand64(); } + uint32_t Rand32() { return Rand64(); } + + // Return a random string consisting of the characters from the specified + // alphabet. + Http2String RandStringWithAlphabet(int length, Http2StringPiece alphabet); + + // STL UniformRandomNumberGenerator implementation. + using result_type = uint64_t; + static constexpr result_type min() { return 0; } + static constexpr result_type max() { + return std::numeric_limits<result_type>::max(); + } + result_type operator()() { return Rand64(); } + + private: + uint8_t key_[32]; + uint32_t counter_ = 0; +}; + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_TEST_TOOLS_HTTP2_RANDOM_H_
diff --git a/http2/test_tools/http2_random_test.cc b/http2/test_tools/http2_random_test.cc new file mode 100644 index 0000000..a1b74f6 --- /dev/null +++ b/http2/test_tools/http2_random_test.cc
@@ -0,0 +1,94 @@ +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" + +#include <set> + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace http2 { +namespace test { +namespace { + +TEST(Http2RandomTest, ProducesDifferentNumbers) { + Http2Random random; + uint64_t value1 = random.Rand64(); + uint64_t value2 = random.Rand64(); + uint64_t value3 = random.Rand64(); + + EXPECT_NE(value1, value2); + EXPECT_NE(value2, value3); + EXPECT_NE(value3, value1); +} + +TEST(Http2RandomTest, StartsWithDifferentKeys) { + Http2Random random1; + Http2Random random2; + + EXPECT_NE(random1.Key(), random2.Key()); + EXPECT_NE(random1.Rand64(), random2.Rand64()); + EXPECT_NE(random1.Rand64(), random2.Rand64()); + EXPECT_NE(random1.Rand64(), random2.Rand64()); +} + +TEST(Http2RandomTest, ReproducibleRandom) { + Http2Random random; + uint64_t value1 = random.Rand64(); + uint64_t value2 = random.Rand64(); + + Http2Random clone_random(random.Key()); + EXPECT_EQ(clone_random.Key(), random.Key()); + EXPECT_EQ(value1, clone_random.Rand64()); + EXPECT_EQ(value2, clone_random.Rand64()); +} + +TEST(Http2RandomTest, STLShuffle) { + Http2Random random; + const Http2String original = "abcdefghijklmonpqrsuvwxyz"; + + Http2String shuffled = original; + std::shuffle(shuffled.begin(), shuffled.end(), random); + EXPECT_NE(original, shuffled); +} + +TEST(Http2RandomTest, RandFloat) { + Http2Random random; + for (int i = 0; i < 10000; i++) { + float value = random.RandFloat(); + ASSERT_GE(value, 0.f); + ASSERT_LE(value, 1.f); + } +} + +TEST(Http2RandomTest, RandStringWithAlphabet) { + Http2Random random; + Http2String str = random.RandStringWithAlphabet(1000, "xyz"); + EXPECT_EQ(1000u, str.size()); + + std::set<char> characters(str.begin(), str.end()); + EXPECT_THAT(characters, testing::ElementsAre('x', 'y', 'z')); +} + +TEST(Http2RandomTest, SkewedLow) { + Http2Random random; + constexpr size_t kMax = 1234; + for (int i = 0; i < 10000; i++) { + size_t value = random.RandomSizeSkewedLow(kMax); + ASSERT_GE(value, 0u); + ASSERT_LE(value, kMax); + } +} + +// Checks that SkewedLow() generates full range. This is required, since in +// some unit tests would infinitely loop. +TEST(Http2RandomTest, SkewedLowFullRange) { + Http2Random random; + std::set<size_t> values; + for (int i = 0; i < 1000; i++) { + values.insert(random.RandomSizeSkewedLow(3)); + } + EXPECT_THAT(values, testing::ElementsAre(0, 1, 2, 3)); +} + +} // namespace +} // namespace test +} // namespace http2
diff --git a/http2/tools/http2_frame_builder.cc b/http2/tools/http2_frame_builder.cc new file mode 100644 index 0000000..1dfcdeb --- /dev/null +++ b/http2/tools/http2_frame_builder.cc
@@ -0,0 +1,181 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h" + +#ifdef WIN32 +#include <winsock2.h> // for htonl() functions +#else +#include <arpa/inet.h> +#include <netinet/in.h> // for htonl, htons +#endif + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { +namespace test { + +Http2FrameBuilder::Http2FrameBuilder(Http2FrameType type, + uint8_t flags, + uint32_t stream_id) { + AppendUInt24(0); // Frame payload length, unknown so far. + Append(type); + AppendUInt8(flags); + AppendUInt31(stream_id); +} + +Http2FrameBuilder::Http2FrameBuilder(const Http2FrameHeader& v) { + Append(v); +} + +void Http2FrameBuilder::Append(Http2StringPiece s) { + Http2StrAppend(&buffer_, s); +} + +void Http2FrameBuilder::AppendBytes(const void* data, uint32_t num_bytes) { + Append(Http2StringPiece(static_cast<const char*>(data), num_bytes)); +} + +void Http2FrameBuilder::AppendZeroes(size_t num_zero_bytes) { + char zero = 0; + buffer_.append(num_zero_bytes, zero); +} + +void Http2FrameBuilder::AppendUInt8(uint8_t value) { + AppendBytes(&value, 1); +} + +void Http2FrameBuilder::AppendUInt16(uint16_t value) { + value = htons(value); + AppendBytes(&value, 2); +} + +void Http2FrameBuilder::AppendUInt24(uint32_t value) { + // Doesn't make sense to try to append a larger value, as that doesn't + // simulate something an encoder could do (i.e. the other 8 bits simply aren't + // there to be occupied). + EXPECT_EQ(value, value & 0xffffff); + value = htonl(value); + AppendBytes(reinterpret_cast<char*>(&value) + 1, 3); +} + +void Http2FrameBuilder::AppendUInt31(uint32_t value) { + // If you want to test the high-bit being set, call AppendUInt32 instead. + uint32_t tmp = value & StreamIdMask(); + EXPECT_EQ(value, value & StreamIdMask()) + << "High-bit of uint32_t should be clear."; + value = htonl(tmp); + AppendBytes(&value, 4); +} + +void Http2FrameBuilder::AppendUInt32(uint32_t value) { + value = htonl(value); + AppendBytes(&value, sizeof(value)); +} + +void Http2FrameBuilder::Append(Http2ErrorCode error_code) { + AppendUInt32(static_cast<uint32_t>(error_code)); +} + +void Http2FrameBuilder::Append(Http2FrameType type) { + AppendUInt8(static_cast<uint8_t>(type)); +} + +void Http2FrameBuilder::Append(Http2SettingsParameter parameter) { + AppendUInt16(static_cast<uint16_t>(parameter)); +} + +void Http2FrameBuilder::Append(const Http2FrameHeader& v) { + AppendUInt24(v.payload_length); + Append(v.type); + AppendUInt8(v.flags); + AppendUInt31(v.stream_id); +} + +void Http2FrameBuilder::Append(const Http2PriorityFields& v) { + // The EXCLUSIVE flag is the high-bit of the 32-bit stream dependency field. + uint32_t tmp = v.stream_dependency & StreamIdMask(); + EXPECT_EQ(tmp, v.stream_dependency); + if (v.is_exclusive) { + tmp |= 0x80000000; + } + AppendUInt32(tmp); + + // The PRIORITY frame's weight field is logically in the range [1, 256], + // but is encoded as a byte in the range [0, 255]. + ASSERT_LE(1u, v.weight); + ASSERT_LE(v.weight, 256u); + AppendUInt8(v.weight - 1); +} + +void Http2FrameBuilder::Append(const Http2RstStreamFields& v) { + Append(v.error_code); +} + +void Http2FrameBuilder::Append(const Http2SettingFields& v) { + Append(v.parameter); + AppendUInt32(v.value); +} + +void Http2FrameBuilder::Append(const Http2PushPromiseFields& v) { + AppendUInt31(v.promised_stream_id); +} + +void Http2FrameBuilder::Append(const Http2PingFields& v) { + AppendBytes(v.opaque_bytes, sizeof Http2PingFields::opaque_bytes); +} + +void Http2FrameBuilder::Append(const Http2GoAwayFields& v) { + AppendUInt31(v.last_stream_id); + Append(v.error_code); +} + +void Http2FrameBuilder::Append(const Http2WindowUpdateFields& v) { + EXPECT_NE(0u, v.window_size_increment) << "Increment must be non-zero."; + AppendUInt31(v.window_size_increment); +} + +void Http2FrameBuilder::Append(const Http2AltSvcFields& v) { + AppendUInt16(v.origin_length); +} + +// Methods for changing existing buffer contents. + +void Http2FrameBuilder::WriteAt(Http2StringPiece s, size_t offset) { + ASSERT_LE(offset, buffer_.size()); + size_t len = offset + s.size(); + if (len > buffer_.size()) { + buffer_.resize(len); + } + for (size_t ndx = 0; ndx < s.size(); ++ndx) { + buffer_[offset + ndx] = s[ndx]; + } +} + +void Http2FrameBuilder::WriteBytesAt(const void* data, + uint32_t num_bytes, + size_t offset) { + WriteAt(Http2StringPiece(static_cast<const char*>(data), num_bytes), offset); +} + +void Http2FrameBuilder::WriteUInt24At(uint32_t value, size_t offset) { + ASSERT_LT(value, static_cast<uint32_t>(1 << 24)); + value = htonl(value); + WriteBytesAt(reinterpret_cast<char*>(&value) + 1, sizeof(value) - 1, offset); +} + +void Http2FrameBuilder::SetPayloadLength(uint32_t payload_length) { + WriteUInt24At(payload_length, 0); +} + +size_t Http2FrameBuilder::SetPayloadLength() { + EXPECT_GE(size(), Http2FrameHeader::EncodedSize()); + uint32_t payload_length = size() - Http2FrameHeader::EncodedSize(); + SetPayloadLength(payload_length); + return payload_length; +} + +} // namespace test +} // namespace http2
diff --git a/http2/tools/http2_frame_builder.h b/http2/tools/http2_frame_builder.h new file mode 100644 index 0000000..c36cb56 --- /dev/null +++ b/http2/tools/http2_frame_builder.h
@@ -0,0 +1,101 @@ +// 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_TOOLS_HTTP2_FRAME_BUILDER_H_ +#define QUICHE_HTTP2_TOOLS_HTTP2_FRAME_BUILDER_H_ + +// Http2FrameBuilder builds wire-format HTTP/2 frames (or fragments thereof) +// from components. +// +// 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 DCHECKs. + +#include <stddef.h> // for size_t + +#include <cstdint> + +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { +namespace test { + +class Http2FrameBuilder { + public: + Http2FrameBuilder(Http2FrameType type, uint8_t flags, uint32_t stream_id); + explicit Http2FrameBuilder(const Http2FrameHeader& v); + Http2FrameBuilder() {} + ~Http2FrameBuilder() {} + + size_t size() const { return buffer_.size(); } + const Http2String& buffer() const { return buffer_; } + + //---------------------------------------------------------------------------- + // Methods for appending to the end of the buffer. + + // Append a sequence of bytes from various sources. + void Append(Http2StringPiece s); + void AppendBytes(const void* data, uint32_t num_bytes); + + // Append an array of type T[N] to the string. Intended for tests with arrays + // initialized from literals, such as: + // const char kData[] = {0x12, 0x23, ...}; + // builder.AppendBytes(kData); + template <typename T, size_t N> + void AppendBytes(T (&buf)[N]) { + AppendBytes(buf, N * sizeof(buf[0])); + } + + // Support for appending padding. Does not read or write the Pad Length field. + void AppendZeroes(size_t num_zero_bytes); + + // Append various sizes of unsigned integers. + void AppendUInt8(uint8_t value); + void AppendUInt16(uint16_t value); + void AppendUInt24(uint32_t value); + void AppendUInt31(uint32_t value); + void AppendUInt32(uint32_t value); + + // Append various enums. + void Append(Http2ErrorCode error_code); + void Append(Http2FrameType type); + void Append(Http2SettingsParameter parameter); + + // Append various structures. + void Append(const Http2FrameHeader& v); + void Append(const Http2PriorityFields& v); + void Append(const Http2RstStreamFields& v); + void Append(const Http2SettingFields& v); + void Append(const Http2PushPromiseFields& v); + void Append(const Http2PingFields& v); + void Append(const Http2GoAwayFields& v); + void Append(const Http2WindowUpdateFields& v); + void Append(const Http2AltSvcFields& v); + + // Methods for changing existing buffer contents (mostly focused on updating + // the payload length). + + void WriteAt(Http2StringPiece s, size_t offset); + void WriteBytesAt(const void* data, uint32_t num_bytes, size_t offset); + void WriteUInt24At(uint32_t value, size_t offset); + + // Set the payload length to the specified size. + void SetPayloadLength(uint32_t payload_length); + + // Sets the payload length to the size of the buffer minus the size of + // the frame header. + size_t SetPayloadLength(); + + private: + Http2String buffer_; +}; + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_TOOLS_HTTP2_FRAME_BUILDER_H_
diff --git a/http2/tools/random_decoder_test.cc b/http2/tools/random_decoder_test.cc new file mode 100644 index 0000000..623487c --- /dev/null +++ b/http2/tools/random_decoder_test.cc
@@ -0,0 +1,167 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +#include <stddef.h> + +#include <algorithm> +#include <memory> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" + +using ::testing::AssertionFailure; +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; + +namespace http2 { +namespace test { + +RandomDecoderTest::RandomDecoderTest() = default; + +bool RandomDecoderTest::StopDecodeOnDone() { + return stop_decode_on_done_; +} + +DecodeStatus RandomDecoderTest::DecodeSegments(DecodeBuffer* original, + const SelectSize& select_size) { + DecodeStatus status = DecodeStatus::kDecodeInProgress; + bool first = true; + VLOG(2) << "DecodeSegments: input size=" << original->Remaining(); + while (first || original->HasData()) { + size_t remaining = original->Remaining(); + size_t size = + std::min(remaining, select_size(first, original->Offset(), remaining)); + DecodeBuffer db(original->cursor(), size); + VLOG(2) << "Decoding " << size << " bytes of " << remaining << " remaining"; + if (first) { + first = false; + status = StartDecoding(&db); + } else { + status = ResumeDecoding(&db); + } + // A decoder MUST consume some input (if any is available), else we could + // get stuck in infinite loops. + if (db.Offset() == 0 && db.HasData() && + status != DecodeStatus::kDecodeError) { + ADD_FAILURE() << "Decoder didn't make any progress; db.FullSize=" + << db.FullSize() + << " original.Offset=" << original->Offset(); + return DecodeStatus::kDecodeError; + } + original->AdvanceCursor(db.Offset()); + switch (status) { + case DecodeStatus::kDecodeDone: + if (original->Empty() || StopDecodeOnDone()) { + return DecodeStatus::kDecodeDone; + } + continue; + case DecodeStatus::kDecodeInProgress: + continue; + case DecodeStatus::kDecodeError: + return DecodeStatus::kDecodeError; + } + } + return status; +} + +// Decode |original| multiple times, with different segmentations, validating +// after each decode, returning on the first failure. +AssertionResult RandomDecoderTest::DecodeAndValidateSeveralWays( + DecodeBuffer* original, + bool return_non_zero_on_first, + const Validator& validator) { + const uint32_t original_remaining = original->Remaining(); + VLOG(1) << "DecodeAndValidateSeveralWays - Start, remaining = " + << original_remaining; + uint32_t first_consumed; + { + // Fast decode (no stopping unless decoder does so). + DecodeBuffer input(original->cursor(), original_remaining); + VLOG(2) << "DecodeSegmentsAndValidate with SelectRemaining"; + VERIFY_SUCCESS( + DecodeSegmentsAndValidate(&input, SelectRemaining(), validator)) + << "\nFailed with SelectRemaining; input.Offset=" << input.Offset() + << "; input.Remaining=" << input.Remaining(); + first_consumed = input.Offset(); + } + if (original_remaining <= 30) { + // Decode again, one byte at a time. + DecodeBuffer input(original->cursor(), original_remaining); + VLOG(2) << "DecodeSegmentsAndValidate with SelectOne"; + VERIFY_SUCCESS(DecodeSegmentsAndValidate(&input, SelectOne(), validator)) + << "\nFailed with SelectOne; input.Offset=" << input.Offset() + << "; input.Remaining=" << input.Remaining(); + VERIFY_EQ(first_consumed, input.Offset()) << "\nFailed with SelectOne"; + } + if (original_remaining <= 20) { + // Decode again, one or zero bytes at a time. + DecodeBuffer input(original->cursor(), original_remaining); + VLOG(2) << "DecodeSegmentsAndValidate with SelectZeroAndOne"; + VERIFY_SUCCESS(DecodeSegmentsAndValidate( + &input, SelectZeroAndOne(return_non_zero_on_first), validator)) + << "\nFailed with SelectZeroAndOne"; + VERIFY_EQ(first_consumed, input.Offset()) + << "\nFailed with SelectZeroAndOne; input.Offset=" << input.Offset() + << "; input.Remaining=" << input.Remaining(); + } + { + // Decode again, with randomly selected segment sizes. + DecodeBuffer input(original->cursor(), original_remaining); + VLOG(2) << "DecodeSegmentsAndValidate with SelectRandom"; + VERIFY_SUCCESS(DecodeSegmentsAndValidate( + &input, SelectRandom(return_non_zero_on_first), validator)) + << "\nFailed with SelectRandom; input.Offset=" << input.Offset() + << "; input.Remaining=" << input.Remaining(); + VERIFY_EQ(first_consumed, input.Offset()) << "\nFailed with SelectRandom"; + } + VERIFY_EQ(original_remaining, original->Remaining()); + original->AdvanceCursor(first_consumed); + VLOG(1) << "DecodeAndValidateSeveralWays - SUCCESS"; + return ::testing::AssertionSuccess(); +} + +// static +RandomDecoderTest::SelectSize RandomDecoderTest::SelectZeroAndOne( + bool return_non_zero_on_first) { + std::shared_ptr<bool> zero_next(new bool); + *zero_next = !return_non_zero_on_first; + return [zero_next](bool first, size_t offset, size_t remaining) -> size_t { + if (*zero_next) { + *zero_next = false; + return 0; + } else { + *zero_next = true; + return 1; + } + }; +} + +RandomDecoderTest::SelectSize RandomDecoderTest::SelectRandom( + bool return_non_zero_on_first) { + return [this, return_non_zero_on_first](bool first, size_t offset, + size_t remaining) -> size_t { + uint32_t r = random_.Rand32(); + if (first && return_non_zero_on_first) { + CHECK_LT(0u, remaining); + if (remaining == 1) { + return 1; + } + return 1 + (r % remaining); // size in range [1, remaining). + } + return r % (remaining + 1); // size in range [0, remaining]. + }; +} + +uint32_t RandomDecoderTest::RandStreamId() { + return random_.Rand32() & StreamIdMask(); +} + +} // namespace test +} // namespace http2
diff --git a/http2/tools/random_decoder_test.h b/http2/tools/random_decoder_test.h new file mode 100644 index 0000000..36f3196 --- /dev/null +++ b/http2/tools/random_decoder_test.h
@@ -0,0 +1,258 @@ +// 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_TOOLS_RANDOM_DECODER_TEST_H_ +#define QUICHE_HTTP2_TOOLS_RANDOM_DECODER_TEST_H_ + +// RandomDecoderTest is a base class for tests of decoding various kinds +// of HTTP/2 and HPACK encodings. + +// TODO(jamessynge): Move more methods into .cc file. + +#include <stddef.h> + +#include <cstdint> +#include <functional> +#include <memory> +#include <type_traits> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" + +namespace http2 { +namespace test { + +// Some helpers. + +template <typename T, size_t N> +Http2StringPiece ToStringPiece(T (&data)[N]) { + return Http2StringPiece(reinterpret_cast<const char*>(data), N * sizeof(T)); +} + +// Overwrite the enum with some random value, probably not a valid value for +// the enum type, but which fits into its storage. +template <typename T, + typename E = typename std::enable_if<std::is_enum<T>::value>::type> +void CorruptEnum(T* out, Http2Random* rng) { + // Per cppreference.com, if the destination type of a static_cast is + // smaller than the source type (i.e. type of r and uint32 below), the + // resulting value is the smallest unsigned value equal to the source value + // modulo 2^n, where n is the number of bits used to represent the + // destination type unsigned U. + using underlying_type_T = typename std::underlying_type<T>::type; + using unsigned_underlying_type_T = + typename std::make_unsigned<underlying_type_T>::type; + auto r = static_cast<unsigned_underlying_type_T>(rng->Rand32()); + *out = static_cast<T>(r); +} + +// Base class for tests of the ability to decode a sequence of bytes with +// various boundaries between the DecodeBuffers provided to the decoder. +class RandomDecoderTest : public ::testing::Test { + public: + // SelectSize returns the size of the next DecodeBuffer to be passed to the + // decoder. Note that RandomDecoderTest allows that size to be zero, though + // some decoders can't deal with that on the first byte, hence the |first| + // parameter. + typedef std::function<size_t(bool first, size_t offset, size_t remaining)> + SelectSize; + + // Validator returns an AssertionResult so test can do: + // EXPECT_THAT(DecodeAndValidate(..., validator)); + typedef ::testing::AssertionResult AssertionResult; + typedef std::function<AssertionResult(const DecodeBuffer& input, + DecodeStatus status)> + Validator; + typedef std::function<AssertionResult()> NoArgValidator; + + RandomDecoderTest(); + + protected: + // TODO(jamessynge): Modify StartDecoding, etc. to (somehow) return + // AssertionResult so that the VERIFY_* methods exported from + // gunit_helpers.h can be widely used. + + // Start decoding; call allows sub-class to Reset the decoder, or deal with + // the first byte if that is done in a unique fashion. Might be called with + // a zero byte buffer. + virtual DecodeStatus StartDecoding(DecodeBuffer* db) = 0; + + // Resume decoding of the input after a prior call to StartDecoding, and + // possibly many calls to ResumeDecoding. + virtual DecodeStatus ResumeDecoding(DecodeBuffer* db) = 0; + + // Return true if a decode status of kDecodeDone indicates that + // decoding should stop. + virtual bool StopDecodeOnDone(); + + // Decode buffer |original| until we run out of input, or kDecodeDone is + // returned by the decoder AND StopDecodeOnDone() returns true. Segments + // (i.e. cuts up) the original DecodeBuffer into (potentially) smaller buffers + // by calling |select_size| to decide how large each buffer should be. + // We do this to test the ability to deal with arbitrary boundaries, as might + // happen in transport. + // Returns the final DecodeStatus. + DecodeStatus DecodeSegments(DecodeBuffer* original, + const SelectSize& select_size); + + // Decode buffer |original| until we run out of input, or kDecodeDone is + // returned by the decoder AND StopDecodeOnDone() returns true. Segments + // (i.e. cuts up) the original DecodeBuffer into (potentially) smaller buffers + // by calling |select_size| to decide how large each buffer should be. + // We do this to test the ability to deal with arbitrary boundaries, as might + // happen in transport. + // Invokes |validator| with the final decode status and the original decode + // buffer, with the cursor advanced as far as has been consumed by the decoder + // and returns validator's result. + ::testing::AssertionResult DecodeSegmentsAndValidate( + DecodeBuffer* original, + const SelectSize& select_size, + const Validator& validator) { + DecodeStatus status = DecodeSegments(original, select_size); + VERIFY_AND_RETURN_SUCCESS(validator(*original, status)); + } + + // Returns a SelectSize function for fast decoding, i.e. passing all that + // is available to the decoder. + static SelectSize SelectRemaining() { + return [](bool first, size_t offset, size_t remaining) -> size_t { + return remaining; + }; + } + + // Returns a SelectSize function for decoding a single byte at a time. + static SelectSize SelectOne() { + return + [](bool first, size_t offset, size_t remaining) -> size_t { return 1; }; + } + + // Returns a SelectSize function for decoding a single byte at a time, where + // zero byte buffers are also allowed. Alternates between zero and one. + static SelectSize SelectZeroAndOne(bool return_non_zero_on_first); + + // Returns a SelectSize function for decoding random sized segments. + SelectSize SelectRandom(bool return_non_zero_on_first); + + // Decode |original| multiple times, with different segmentations of the + // decode buffer, validating after each decode, and confirming that they + // each decode the same amount. Returns on the first failure, else returns + // success. + AssertionResult DecodeAndValidateSeveralWays(DecodeBuffer* original, + bool return_non_zero_on_first, + const Validator& validator); + + static Validator ToValidator(std::nullptr_t) { + return [](const DecodeBuffer& input, DecodeStatus status) { + return ::testing::AssertionSuccess(); + }; + } + + static Validator ToValidator(const Validator& validator) { + if (validator == nullptr) { + return ToValidator(nullptr); + } + return validator; + } + + static Validator ToValidator(const NoArgValidator& validator) { + if (validator == nullptr) { + return ToValidator(nullptr); + } + return [validator](const DecodeBuffer& input, DecodeStatus status) { + return validator(); + }; + } + + // Wraps a validator with another validator + // that first checks that the DecodeStatus is kDecodeDone and + // that the DecodeBuffer is empty. + // TODO(jamessynge): Replace this overload with the next, as using this method + // usually means that the wrapped function doesn't need to be passed the + // DecodeBuffer nor the DecodeStatus. + static Validator ValidateDoneAndEmpty(const Validator& wrapped) { + return [wrapped](const DecodeBuffer& input, + DecodeStatus status) -> AssertionResult { + VERIFY_EQ(status, DecodeStatus::kDecodeDone); + VERIFY_EQ(0u, input.Remaining()) << "\nOffset=" << input.Offset(); + if (wrapped) { + return wrapped(input, status); + } + return ::testing::AssertionSuccess(); + }; + } + static Validator ValidateDoneAndEmpty(NoArgValidator wrapped) { + return [wrapped](const DecodeBuffer& input, + DecodeStatus status) -> AssertionResult { + VERIFY_EQ(status, DecodeStatus::kDecodeDone); + VERIFY_EQ(0u, input.Remaining()) << "\nOffset=" << input.Offset(); + if (wrapped) { + return wrapped(); + } + return ::testing::AssertionSuccess(); + }; + } + static Validator ValidateDoneAndEmpty() { + NoArgValidator validator; + return ValidateDoneAndEmpty(validator); + } + + // Wraps a validator with another validator + // that first checks that the DecodeStatus is kDecodeDone and + // that the DecodeBuffer has the expected offset. + // TODO(jamessynge): Replace this overload with the next, as using this method + // usually means that the wrapped function doesn't need to be passed the + // DecodeBuffer nor the DecodeStatus. + static Validator ValidateDoneAndOffset(uint32_t offset, + const Validator& wrapped) { + return [wrapped, offset](const DecodeBuffer& input, + DecodeStatus status) -> AssertionResult { + VERIFY_EQ(status, DecodeStatus::kDecodeDone); + VERIFY_EQ(offset, input.Offset()) << "\nRemaining=" << input.Remaining(); + if (wrapped) { + return wrapped(input, status); + } + return ::testing::AssertionSuccess(); + }; + } + static Validator ValidateDoneAndOffset(uint32_t offset, + NoArgValidator wrapped) { + return [wrapped, offset](const DecodeBuffer& input, + DecodeStatus status) -> AssertionResult { + VERIFY_EQ(status, DecodeStatus::kDecodeDone); + VERIFY_EQ(offset, input.Offset()) << "\nRemaining=" << input.Remaining(); + if (wrapped) { + return wrapped(); + } + return ::testing::AssertionSuccess(); + }; + } + static Validator ValidateDoneAndOffset(uint32_t offset) { + NoArgValidator validator; + return ValidateDoneAndOffset(offset, validator); + } + + // Expose |random_| as Http2Random so callers don't have to care about which + // sub-class of Http2Random is used, nor can they rely on the specific + // sub-class that RandomDecoderTest uses. + Http2Random& Random() { return random_; } + Http2Random* RandomPtr() { return &random_; } + + uint32_t RandStreamId(); + + bool stop_decode_on_done_ = true; + + private: + Http2Random random_; +}; + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_TOOLS_RANDOM_DECODER_TEST_H_
diff --git a/http2/tools/random_util.cc b/http2/tools/random_util.cc new file mode 100644 index 0000000..82c3edd --- /dev/null +++ b/http2/tools/random_util.cc
@@ -0,0 +1,39 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/tools/random_util.h" + +#include <cmath> + +namespace http2 { +namespace test { + +// Here "word" means something that starts with a lower-case letter, and has +// zero or more additional characters that are numbers or lower-case letters. +Http2String GenerateHttp2HeaderName(size_t len, Http2Random* rng) { + Http2StringPiece alpha_lc = "abcdefghijklmnopqrstuvwxyz"; + // If the name is short, just make it one word. + if (len < 8) { + return rng->RandStringWithAlphabet(len, alpha_lc); + } + // If the name is longer, ensure it starts with a word, and after that may + // have any character in alphanumdash_lc. 4 is arbitrary, could be as low + // as 1. + Http2StringPiece alphanumdash_lc = "abcdefghijklmnopqrstuvwxyz0123456789-"; + return rng->RandStringWithAlphabet(4, alpha_lc) + + rng->RandStringWithAlphabet(len - 4, alphanumdash_lc); +} + +Http2String GenerateWebSafeString(size_t len, Http2Random* rng) { + static const char* kWebsafe64 = + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"; + return rng->RandStringWithAlphabet(len, kWebsafe64); +} + +Http2String GenerateWebSafeString(size_t lo, size_t hi, Http2Random* rng) { + return GenerateWebSafeString(rng->UniformInRange(lo, hi), rng); +} + +} // namespace test +} // namespace http2
diff --git a/http2/tools/random_util.h b/http2/tools/random_util.h new file mode 100644 index 0000000..a2107b7 --- /dev/null +++ b/http2/tools/random_util.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_TOOLS_RANDOM_UTIL_H_ +#define QUICHE_HTTP2_TOOLS_RANDOM_UTIL_H_ + +#include <stddef.h> + +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" + +namespace http2 { +namespace test { + +// Generate a string with the allowed character set for HTTP/2 / HPACK header +// names. +Http2String GenerateHttp2HeaderName(size_t len, Http2Random* rng); + +// Generate a string with the web-safe string character set of specified len. +Http2String GenerateWebSafeString(size_t len, Http2Random* rng); + +// Generate a string with the web-safe string character set of length [lo, hi). +Http2String GenerateWebSafeString(size_t lo, size_t hi, Http2Random* rng); + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_TOOLS_RANDOM_UTIL_H_