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